简体   繁体   中英

Using ASP.NET Identity in an ASP.NET Core MVC application without Entity Framework and Migrations

Is it possible to use ASP.NET Identity without Entity Framework and Entity Framework migrations? The rest of my application will be using a Micro ORM for data access. However, the application is using the built in ASP.NET Identity Individual User accounts.

My goal is to still be able to use the built in UserManager and LoginManager classes and additionally retrieve a list of the Users using the Micro ORM and do away with anything to do with EF/Migrations. Is this possible? It doesn't seem like it is since the original database structure is created by Applying the initial migration.

If someone has a good technique for doing this, please share.

First you need to create a custom user Store:

public class UserStore : IUserStore<IdentityUser>,
                         IUserClaimStore<IdentityUser>,
                         IUserLoginStore<IdentityUser>,
                         IUserRoleStore<IdentityUser>,
                         IUserPasswordStore<IdentityUser>,
                         IUserSecurityStampStore<IdentityUser>
{
    // interface implementations not shown
}

Then you need to register it into the dependency injection container:

// Add identity types
services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddDefaultTokenProviders();

// Identity Services
services.AddTransient<IUserStore<ApplicationUser>, CustomUserStore>();
services.AddTransient<IRoleStore<ApplicationRole>, CustomRoleStore>();

This is documented here .

Asp.Net Identity has abstracted away the stores it needs and documentation on their stores is here;
https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-custom-storage-providers

This is an example of a store;

public class InMemoryUserStore<TUser> :
        IUserStore<TUser>,
        IUserLoginStore<TUser>,
        IUserClaimStore<TUser>,
        IUserPasswordStore<TUser>,
        IUserSecurityStampStore<TUser>,
        IUserTwoFactorStore<TUser>,
        IUserEmailStore<TUser>,
        IUserLockoutStore<TUser>,
        IUserAuthenticatorKeyStore<TUser>,
        IUserTwoFactorRecoveryCodeStore<TUser>,
        IUserPhoneNumberStore<TUser> where TUser: MemoryIdentityUser
    {
        ...
    }

You can also have your own User object, and it doesn't have to inherit from anything.

public class MemoryIdentityUser
{
    private List<MemoryUserClaim> _claims;
    private List<MemoryUserLogin> _logins;
    private List<MemoryUserToken> _tokens;
    ...
}

Asp.Net Identity is an engine and as such is opinionated. It is that opinion that drove the abstractions of the stores. I wish the Asp.Net Identity docs has full sequence diagrams as to how it interacts with the stores. At a minimum a few reference sequences that have to be honored.

The store has some quirks where it has required methods that are only to mutate private data in the implementation and then followed up by update calls that assume you will commit that data to persistent storage.

You might want to check out this project; https://github.com/ghstahl/AspNetCore.2.InMemoryIdentity

You can see what you need to do without the burden of having a database.

Hooking it up;

// My user is custom, so I made ApplicationUser inherit
public class ApplicationUser : MemoryIdentityUser
{
}

Startup.cs;

public void ConfigureServices(IServiceCollection services)
{
  services.AddSingleton<IUserStore<ApplicationUser>>(provider =>
  {
    return new InMemoryUserStore<ApplicationUser>();
  });
  services.AddIdentity<ApplicationUser>(Configuration)
          .AddDefaultTokenProviders();

  // Add application services.
  services.AddTransient<IEmailSender, EmailSender>();

  services.AddMvc();
}

In AddIdentity, the following illustrates to the extent you can bring in your own implementations

public static class InMemoryIdentityServiceCollectionExtensions
{
    public static IdentityBuilder AddIdentity<TUser>(this IServiceCollection services, IConfiguration configuration)
        where TUser : class => services.AddIdentity<TUser>(configuration,null);

    public static IdentityBuilder AddIdentity<TUser>(this IServiceCollection services, IConfiguration configuration,Action<IdentityOptions> setupAction)
        where TUser : class
    {
        // Services used by identity
        var authenticationBuilder = services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
                options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
                options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
            })
            .AddCookie(IdentityConstants.ApplicationScheme, o =>
            {
                o.LoginPath = new PathString("/Account/Login");
                o.Events = new CookieAuthenticationEvents
                {
                    OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
                };
            })
            .AddCookie(IdentityConstants.ExternalScheme, o =>
            {
                o.Cookie.Name = IdentityConstants.ExternalScheme;
                o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
            })
            .AddCookie(IdentityConstants.TwoFactorRememberMeScheme,
                o => o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme)
            .AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
            {
                o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
                o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
            });

        // Hosting doesn't add IHttpContextAccessor by default
        services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

        // Identity services
        services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
        services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
        services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
        services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();

        // No interface for the error describer so we can add errors without rev'ing the interface
        services.TryAddScoped<IdentityErrorDescriber>();
        services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>();
        services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser>>();
        services.TryAddScoped<UserManager<TUser>, AspNetUserManager<TUser>>();
        services.TryAddScoped<SignInManager<TUser>, SignInManager<TUser>>();

        if (setupAction != null)
        {
            services.Configure(setupAction);
        }

        return new IdentityBuilder(typeof(TUser), services);
    }
}

There are a bunch of IUserStore implementations out there, with every type of backing database. I copied my InMemoryUserStore from another project that was using MongoDB as a backing DB.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM