Identity 2.0 with custom tables

32,704

Solution 1

In your examples, you seem to want to replace the DbContext with something else but I believe that actually, you need to focus your efforts one level higher.

The framework has a class UserManager which has the responsibility for managing, but not storing, the users and their related information. When it wants to store the users (or their related information) the default is to use the provided UserStore<IdentityUser> which knows how to store IdentityUser instances in a database using a DbContext.

In the 2.0 framework, the various bits of the identity storing were broken into several interfaces. The default implementation provided, UserStore<IdentityUser> implements several of these interfaces and stores the data for them all in the database using a DBContext.

If you look at the defnition of the default provided entity framework based UserStore you can see that it implements many of these small interfaces:

public class UserStore<TUser, TRole, TKey, TUserLogin, TUserRole, TUserClaim> : IUserLoginStore<TUser, TKey>, 
IUserClaimStore<TUser, TKey>, IUserRoleStore<TUser, TKey>, IUserPasswordStore<TUser, TKey>, 
IUserSecurityStampStore<TUser, TKey>, IQueryableUserStore<TUser, TKey>, IUserEmailStore<TUser, TKey>, 
IUserPhoneNumberStore<TUser, TKey>, IUserTwoFactorStore<TUser, TKey>, IUserLockoutStore<TUser, TKey>, 
IUserStore<TUser, TKey>, IDisposable 
where TUser : IdentityUser<TKey, TUserLogin, TUserRole, TUserClaim>
where TRole : IdentityRole<TKey, TUserRole>
where TKey : Object, IEquatable<TKey>
where TUserLogin : new(), IdentityUserLogin<TKey>
where TUserRole : new(), IdentityUserRole<TKey>
where TUserClaim : new(), IdentityUserClaim<TKey>

As you don't want to use Entity Framework at all, you need to provide your own implementations of some of these key interfaces which will store that data in the places you want the data to be stored. The main interface is IUserStore<Tuser,TKey>. this defines the contract for storing users which have a key of a particular type. If you only want to store users and no other information then you can implement this interface and then pass your implementation to the UserManager and you should be good to go.

It's unlikely that this will be enough though as likely you will want passwords, roles, logins etc for your users. If so then you need to make your class which implements IUserStore<Tuser,TKey> also implement IUserPasswordStore<TUser, TKey>, IRoleStore<TRole, TKey> and IUserClaimStore<TUser, TKey>.

You can find a list of all the interfaces on MSDN here

Depending on the bits you want to use you need to implement those interfaces.

You will probably also need to define your own versions of the Identity* classes which exist for the Entity framework already out of the box. So you will need your own IdentityUser class which represents the users you want to store, and IdentityUserClaim for the users claims. I haven't done this myself so I'm not exactly sure, but my feeling is that you will have to do this.

Scott Allen has a good article on the various bits of the model which might be useful.

As for your issues with this line:

AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, await user.GenerateUserIdentityAsync(UserManager));

There is a method called GenerateUserIdentityAsync which is defined on Applicationuser which extends IdentityUser. It is defined in the template project I believe and looks something like this:

public class ApplicationUser : IdentityUser 
{    
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
    UserManager<ApplicationUser> manager) {
    // Note the authenticationType must match the one 
    // defined in CookieAuthenticationOptions.AuthenticationType
    var userIdentity = 
        await manager.CreateIdentityAsync(this, 
            DefaultAuthenticationTypes.ApplicationCookie);
    // Add custom user claims here
    return userIdentity;
    }
}

As you are not using the entity framework IdentityUser any more then you either need to define a similar method or your own User class or implement the same functionality some other way (like just calling await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie) instead)

Solution 2

I am pretty new to Identity myself. Does your User entity implement the IUser interface? e.g.

public partial class User : IUser<Guid> //Whatever your key is
{
    public Task<ClaimsIdentity> GenerateUserIdentityAsync(ApplicationUserManager manager)
    {
        return Task.FromResult(GenerateUserIdentity(manager));
    }

    public ClaimsIdentity GenerateUserIdentity(ApplicationUserManager manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = manager.CreateIdentity<User, Guid>(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }
}

Then to sign in you can call the above:

public async Task SignInAsync(User user, bool isPersistent)
    {
        var userIdentity = await user.GenerateUserIdentityAsync(UserManager);
        AuthenticationManager.SignIn(
            new AuthenticationProperties
            {
                IsPersistent = isPersistent
            },
            userIdentity
        );
    }   

Solution 3

I think this has been updated recently to this in ApplicationUser:

public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType)
{
    // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
    var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
    // Add custom user claims here
    return userIdentity;
}

It is called like this from AccountController -> GetExternalLogin

ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
    OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
    CookieAuthenticationDefaults.AuthenticationType);
Share:
32,704
joshhendo
Author by

joshhendo

Currently completing a Master of Computer Science degree part time.

Updated on June 08, 2020

Comments

  • joshhendo
    joshhendo almost 4 years

    I'm new to ASP.NET identity and am still trying to get my head around how it all works. Unfortunately I've found many of the tutorials I've tried are for Identity 1.0, whereas I'm attempting to work with Identity 2.0.

    The biggest problem I am facing is something I thought would be simple, but has turned out not to be. What I'm trying to do is use an existing database with an existing user table. Currently all I can seem to do is get Identity to create it's own tables to store user data. I don't want to use Entity Framework, but I've had great difficulty separating Entity Framework from Identity 2.0.

    While my situation I'm using SQL Server, I thought I'd try and implement the custom provider for MySQL at this link: http://www.asp.net/identity/overview/extensibility/implementing-a-custom-mysql-aspnet-identity-storage-provider and https://github.com/raquelsa/AspNet.Identity.MySQL. This appears to work under Identity 1 only. There does appear to be a newer one, however that uses Entity Framework. I've also tried https://github.com/ILMServices/RavenDB.AspNet.Identity.

    With everything I've tried, I get stuck with the following line:

    var manager = new IdentityUserManager(new UserStore<IdentityUser>(context.Get<ApplicationDbContext>()));
    

    The problem I'm facing is that I need to remove ApplicaitonDbContext class according to the instructions for RavenDB, however I don't know what to put in this line instead.

    The errors I get are:

    The non-generic type 'AspNet.Identity.MySQL.UserStore' cannot be used with type arguments

    and

    The type or namespace name 'ApplicationDbContext' could not be found (are you missing a using directive or an assembly reference?)

    The second error is to be expected, however I'm not sure what to use in this line of code instead. Has anyone had any experience with implementing a custom provider without using Entity Framework?

    Thanks.

    Update 1:

    I've attempted the following tutorial [ http://aspnetguru.com/customize-authentication-to-your-own-set-of-tables-in-asp-net-mvc-5/ ] but it appears to be incomplete, with compile or runtime errors when following the tutorial exactly. A few problems I still have are...

    When replacing all instances of "ApplicationUser" with "User" have a problem with the following line in IdentityConfig.cs

    var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<MyDbContext>()));
    

    Changing ApplicationUser to User produces the following error

    The type 'WebApplicationTest2.Models.User' cannot be used as type parameter 'TUser' in the generic type or method 'Microsoft.AspNet.Identity.EntityFramework.UserStore'. There is no implicit reference conversion from 'WebApplicationTest2.Models.User' to 'Microsoft.AspNet.Identity.EntityFramework.IdentityUser'.

    I'm also having difficulty working out how to use the user manager and many of the ASync methods. The following lines don't work:

    AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, await user.GenerateUserIdentityAsync(UserManager));
    

    In AccountController.cs with the following errors:

    The best overloaded method match for 'Microsoft.Owin.Security.IAuthenticationManager.SignIn(Microsoft.Owin.Security.AuthenticationProperties, params System.Security.Claims.ClaimsIdentity[])' has some invalid arguments.

    'WebApplicationTest2.Models.User' does not contain a definition for 'GenerateUserIdentityAsync' and no extension method 'GenerateUserIdentityAsync' accepting a first argument of type 'WebApplicationTest2.Models.User' could be found (are you missing a using directive or an assembly reference?)