Using a Stefan Cebulak's answer and a Ben Foster's great blog article ASP.NET Identity Stripped Bare I have came up with below solution, which I have applied to ASP.NET Identity 2.0 with a generated by Visual Studio 2013 AccountController
.
The solution uses an integer as a primary key for users and also allows to get an ID of currently logged in user without making a trip to the database.
Here are the steps, you need to follow:
1. Create custom user-related classes
By default, the AccountController
uses classes, which are using string
, as a type of a primary key. We need to create below classes, which will use an int
instead. I have defined all below classes in one file: AppUser.cs
public class AppUser :
IdentityUser<int, AppUserLogin, AppUserRole, AppUserClaim>,
IUser<int>
{
}
public class AppUserLogin : IdentityUserLogin<int> { }
public class AppUserRole : IdentityUserRole<int> { }
public class AppUserClaim : IdentityUserClaim<int> { }
public class AppRole : IdentityRole<int, AppUserRole> { }
It will also be useful, to have a custom ClaimsPrincipal, which will easily expose User's ID
public class AppClaimsPrincipal : ClaimsPrincipal
{
public AppClaimsPrincipal( ClaimsPrincipal principal ) : base( principal )
{ }
public int UserId
{
get { return int.Parse(this.FindFirst( ClaimTypes.Sid ).Value); }
}
}
2. Create a custom IdentityDbContext
Our application's database context will extend IdentityDbContext
, which implements by default all authentication-related DbSets. Even if DbContext.OnModelCreating
is an empty method, I am not sure about the IdentityDbContext.OnModelCreating
, so when overriding, remember to call base.OnModelCreating( modelBuilder )
AppDbContext.cs
public class AppDbContext :
IdentityDbContext<AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
public AppDbContext() : base("DefaultConnection")
{
// Here use initializer of your choice
Database.SetInitializer( new CreateDatabaseIfNotExists<AppDbContext>() );
}
// Here you define your own DbSet's
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
base.OnModelCreating( modelBuilder );
// Here you can put FluentAPI code or add configuration map's
}
}
3. Create custom UserStore
and UserManager
, which will use above
AppUserStore.cs
public interface IAppUserStore : IUserStore<AppUser, int>
{
}
public class AppUserStore :
UserStore<AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>,
IAppUserStore
{
public AppUserStore() : base( new AppDbContext() )
{
}
public AppUserStore(AppDbContext context) : base(context)
{
}
}
AppUserManager.cs
public class AppUserManager : UserManager<AppUser, int>
{
public AppUserManager( IAppUserStore store ) : base( store )
{
}
}
4. Modify AccountController
to use your custom classes
Change all UserManager
to AppUserManager
, UserStore
to AppUserStore
etc. Take an example of this constructors:
public AccountController()
: this( new AppUserManager( new AppUserStore( new AppDbContext() ) ) )
{
}
public AccountController(AppUserManager userManager)
{
UserManager = userManager;
}
5. Add user's ID as a claim to ClaimIdentity
stored in a cookie
In step 1, we have created AppClaimsPrincipal
, which exposes UserId taken out of ClaimType.Sid
. However, to have this claim available, we need to add it, when logging in the user. In AccountController
a SingInAsync
method is responsible for logging in. We need to add a line to this method, to add the claim.
private async Task SignInAsync(AppUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
// Extend identity claims
identity.AddClaim( new Claim( ClaimTypes.Sid, user.Id.ToString() ) );
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
6. Create a BaseController
with a CurrentUser
property
To have an easy access to a currently logged in user's ID in your controllers, create an abstract BaseController
, from which your controllers will derive. In the BaseController
, create a CurrentUser
as follows:
public abstract class BaseController : Controller
{
public AppClaimsPrincipal CurrentUser
{
get { return new AppClaimsPrincipal( ( ClaimsPrincipal )this.User ); }
}
public BaseController()
{
}
}
7. Inherit your controllers from BaseController
and enjoy
From now on, you can use CurrentUser.UserId
in your controllers to access an ID of a currently logged in user without trips to the database. You can use it, to query only objects, which belong to the user.
You don't have to take care of auto generation of user primary keys - no surprise, Entity Framework by default uses Identity for integer primary keys, when creating tables.
Warning! Keep in mind, that if you implement it in already released project, for already logged in users ClaimsType.Sid
will not exist and FindFirst
will return null in AppClaimsPrincipal
. You need to either force logout all users or handle this scenario in AppClaimsPrincipal