Identity Server 3 using ASP.NET Identity

Scott Brady
Scott Brady
Identity Server ・ Updated June 2016

IdentityServer3 is no longer supported. I highly recommend that you consider moving to IdentityServer4 or Duende IdentityServer.

Identity Server 3 comes with out of the box support for ASP.NET Identity in the form of an existing implementation of the Identity Server IUserService interface. This implementation provides the normal Identity Server behaviour using your average ASP.NET Identity implementation as its user store.

This implementation came out of beta for the v2.0.0 release and whilst it's a little rough around the edges, it provides a solid, extensible user service for getting you started.

In this post I’ll cover how to set up the ASP.NET Identity user service, its default behaviour and also how to implement some common extensibility scenarios.

Example Implementation

To keep things simple we’ll use some of the in-memory implementations from my Identity Server implementation guide, but instead of using the hard-coded InMemoryUsers, we'll be using the AspNetIdentityUserService.

Package

To get access to this user service, we’ll need to install the IdentityServer3.AspNetIdentity package. Now, with the release of v2.0.0, the Identity Server team have started adding the UserService source code directly into your project. They add this and the folder structure of App_Packages\IdentityServer3.AspNetIdentity\IdentityServer3.AspNetIdentity.cs. This class then contains the necessary AspNetIdentityUserService.

I find this strange and not behaviour I would expect from an already fully extensible client library (usually we’d get a dll to reference). My main gripe is that if I went ahead and started modifying their source code as suggested, I’m going to have all of my work overwritten when it comes time to update my packages. Not good.

I'm not a fan of this but I don’t like moaning without presenting a solution, so I’ve repackaged the user service to act like a normal client library, with no volatile data.

Original:

Install-Package IdentityServer3.AspNetIdentity

Or repackaged as client library dll:

Install-Package IdentityServer3.AspNetIdentity.dll

This will automatically pull in the core ASP.NET Identity package. If you want to use the default Entity Framework implementation (e.g. IdentityDbContext, IdentityUser, etc.) you will also need to install Microsoft.AspNet.Identity.EntityFramework.

Registration

All that’s left is to register your chosen ASP.NET Identity implementation. Saying that, here’s another slightly off part of the ASP.NET Identity implementation of Identity Server 3; its DI implementation can’t quite handle the generics of ASP.NET Identity. Though to be fair, the ASP.NET team can barely handle the generics of ASP.NET Identity , but we have a few options of solving this.

At the moment, I can show you three approaches to register your AspNetIdentityUserService:

DI Workaround

With a bit of work we can make the default ASP.NET Identity classes work for us by explicitly stating the constructor to use for the UserManager. Note that we need to use UserManager<IdentityUser, string> like the AspNetIdentityUserManager constructor requires, leaving out the key will cause Autofac (the internal DI container) to error.

factory.Register(new Registration<IdentityDbContext>());
factory.Register(new Registration<UserStore<IdentityUser>>());
factory.Register(new Registration<UserManager<IdentityUser, string>>(
  x => new UserManager<IdentityUser>(x.Resolve<UserStore<IdentityUser>())));

factory.UserService = new Registration<IUserService, AspNetIdentityUserService<IdentityUser, string>>();

Concrete Factory Method

We can achieve the same results using a factory pattern. This works well with the inbuilt dependency injection in both Identity Server and the ASP.NET OWIN service locator. For the sake of this demonstration, I have used a static factory.

public static class UserServiceFactory {
    public static AspNetIdentityUserService<IdentityUser, string> Create() {
        var context = new IdentityDbContext();
        var userStore = new UserStore<IdentityUser>(context);
        var userManager = new UserManager<IdentityUser>(userStore);

return new AspNetIdentityUserService<IdentityUser, string>(userManager); } }
factory.UserService = new Registration<IUserService>(UserServiceFactory.Create());

ASP.NET Identity Concrete Implementation

You can effectively hide the generics of ASP.NET Identity by extending IdentityDbContext, IdentityUser, UserStore, UserManager and AspNetIdentityUserStore with concrete classes that hard-code the generics. Note that you must also extend AspNetIdentityUserStore to get this method to work... You can find an example of this here.

I prefer to use the concrete factory as I get to set up my UserManager exactly as I need it (e.g. lockout policies, password validation, tokenprovider, etc.) in a maintainable way with minimal effort. It also allows for an effective DI solution outside of Identity Server.

And that’s it. Assuming you have users in your identity database, we can start up Identity Server and start logging in.

Default User Service Behavior

Claims

All claims are generated within the GetClaimsFromAccount method. The subject claim is generated from your ASP.NET Identity userId and preferred username from username. Further claims are generated based on whether or not your user store supports UserEmail (email and emailverified claims), UserPhoneNumber (phonenumber and phonenumberverified), UserClaims (all other claims from the identity claims store) and UserRole (roles from your roles table/methods).

The display name claim type can be set explicitly for the user service, otherwise it will automatically run through any of the other usual suspects looking for a name claim type (eventually resorting to the lovely Microsoft XML SOAP claim type).

Authentication

Authentication for username/password is handled in a pretty standard way in the AuthenticateLocalAsync method, however the AuthenicateExternalAsync method is worth dissecting, as you may want to override default behavior before going live.

AuthenticateExternalAsync branches off into two other methods: ProcessNewExternalAccountAsync and ProcessExistingExternalAccountAsync, handling new external users and existing external users respectively.

ProcessNewExternalAccountAsync will automatically create a new user in your identity database with a single UserLogin. This user obviously won’t have a password but they will have claims generated automatically from the information passed back/requested from the authentication provider. A point of contact is generated for you in the form of their email address or their phone number.

Common Extensibility Scenarios

Enforcing Local Signup before allowing external authentication

Many developers and their business requirements cannot allow for users to signup from an external provider. For example, they may need to enforce the collection of certain data before account creation, which an external provider cannot supply.

This can be done by extending the existing user service overriding the ProcessNewExternalAccountAsync method and, quite simply, returning the following:

protected override async Task<AuthenticateResult> ProcessNewExternalAccountAsync(
  string provider, 
  string providerId, 
  IEnumerable<Claim> claims) {
    return new AuthenticateResult("Unable to authenticate. Please sign in to your local account and link your social login to your account.");
}
Denying external authentication with local account