본문 바로가기

.NET/MVC.NET

ASP.NET Identity 2.1 Custom Database(ASP.NET ID 2.1 사용자 지정 데이터베이스)

출처 : http://www.jamessturtevant.com/posts/ASPNET-Identity2.0-Custom-Database/



This is a two post series. You can read the second post at ASPNET Identity Custom Database and OWIN.

I have to admit when I first took a deeper look at the ASP.NET’s Identity model in version 2.1 I was a bit overwhelmed. I was looking for a way to authenticate a user against an existing database using Forms authentication to create an authentication cookie after verifying the user’s credentials. Looking at the template ASP.NET Individual User Accounts I found that the SignInManager was responsible for creating the authentication cookie and so I started there.

I found that the SignInManager constructor required a UserManager which in turned required an implementation of IUserStore. And so I went in search of a way to implement my own UserStore by look at the Entity Framework User Store. When I saw the number of interfaces Entity Frame works UserStore implemented I froze. Would I know what to implement and how?

IUser<TKey>

So I took a few deep breaths and when went back to the SigninManager. I would start there and see what was need to implement it. Using the existing SigninManager as a reference, I found I need to have a IUser<TKey>. This is easy enough:

public class CustomUser : IUser<string>
{
public string Id { get; set; }

public string UserName { get; set; }
}

UserManager<TUser>

Next the SigninManager constructor takes a Usermanager. Again this was a fairly simple implementation:

public class CustomUserManager : UserManager<CustomUser>
{
public CustomUserManager(IUserStore<CustomUser> store)
: base(store)
{
}
}

SigninManager<TUser, TKey>

Now we have all the piece to be able to put together the SigninManager:

public class CustomSignInManager : SignInManager<CustomUser, string>
{
public CustomSignInManager(CustomUserManager userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager)
{
}
}

IUserStore<TUser>

The astute reader will have notice that we missed the implementation of the IUserStore<TUser> for the CustomerUserManager. This again is a very quick implementation of a few CRUD operations (I have left some of the implementation blank and the others are not production ready):

public class CustomUserStore : IUserStore<CustomUser>
{
private CustomDbContext database;

public CustomUserStore()
{
this.database = new CustomDbContext();
}

public void Dispose()
{
this.database.Dispose();
}

public Task CreateAsync(CustomUser user)
{
// TODO
throw new NotImplementedException();
}

public Task UpdateAsync(CustomUser user)
{
// TODO
throw new NotImplementedException();
}

public Task DeleteAsync(CustomUser user)
{
// TODO
throw new NotImplementedException();
}

public async Task<CustomUser> FindByIdAsync(string userId)
{
CustomUser user = await this.database.CustomUsers.Where(c => c.UserId == userId).FirstOrDefaultAsync();
return user;
}

public async Task<CustomUser> FindByNameAsync(string userName)
{
CustomUser user = await this.database.CustomUsers.Where(c => c.UserName == userName).FirstOrDefaultAsync();
return user;
}
}

This is a two post series. You may like to read the first post ASPNET Identity and Custom Database.

In the last post, we covered how to create a custom SigninManager. At first glance there was a lot of work to be down but after diving in we found that there were only a few simple classes we had to set up.

In this post, I will demonstrate how to use OWIN to load our SigninManager for each request. By the end of the post we will have a SigninManager that we can use to create an authentication cookie and sign a user into our system.

OWIN Middleware

The latest version of ASP.NET is setup to use the OWIN middleware. If we take a look the Startup.Auth.cs file in the App_Start folder we will find an example of the out-of-the-box SigninManager configuration:

public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

//left other configuration out for brevity
}

The app.CreatePerOwinContext<T>() creates one instance of the given type per request. This way we only have ApplicationUserManager and one ApplicationSignInManager around for each request. To configure our CustomUserManager and CustomSignInManager we need to create factory methods and add them to the Startup.Auth.cs.

It is important to note the order in which the app.CreatePerOwinContext<T>() is called in the configuration. Since a SigninManager requires a UserManger the call to create the SigninManager on the OWIN context must come after the call to create the UserManager

The app.CreatePerOwinContext<T>() can take two different types of methods (We will see each type of callback method when we create them):

  1. Func<T> createCallback
  2. Func<IdentityFactoryOptions<T>, IOwinContext, T>

UserManager Factory Method

The UserManager method is the first callback method type; a simple static method that returns a new CustomUserManager. It would be possible to configure a method that also returns the CustomUserDataContext and pass it to the store. To keep it simple and show both types of methods app.CreatePerOwinContext<T>() takes I have left it out.

public static CustomUserManager Create()
{
var manager = new CustomUserManager(new CustomUserStore());
return manager;
}

SignInManager Factory Method

The SigninManager has a the second callback type but is again not too complex:

public static CustomSignInManager Create(IdentityFactoryOptions<CustomSignInManager> options, IOwinContext context)
{
return new CustomSignInManager(context.GetUserManager<CustomUserManager>(), context.Authentication);
}

OWIN Configuration

The configuration is very similar to the out-of-the-box configuration. In the Startup.Auth.cs file we add our custom managers:

public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

// Add our custom managers
app.CreatePerOwinContext<CustomUserManager>(CustomUserManager.Create);
app.CreatePerOwinContext<CustomSignInManager>(CustomSignInManager.Create);
}

Use in a Controller

And to use it in a Controller add the following property to the Controller class which uses the HttpContext to get our single instance per request. The private set allows you to create a Controller Constructor where you can pass a mock instance for testing.

private CustomSignInManager _signInManager;
public CustomSignInManager CustomSignInManager
{
get
{
return _signInManager ?? HttpContext.GetOwinContext().Get<CustomSignInManager>();
}
private set { _signInManager = value; }
}

Now in your your Login Action use it:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
// do model validations, password compare and other checks then sign them in

//creates and signs a cookie.
CustomSignInManager.SignIn(customUser, isPersistent: true, rememberBrowser: false);
}

Hope that helps clear up some of the steps that are required to create an authentication cookie against an existing database.