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 InMemoryUser
s, 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.");
}