Service API authentication with new AspNetIdentity OWIN

EPiServer recently released a package which adds support for ASP.NET identity in the CMS project. While it works great with CMS UI authentication, Service API configuration is a little bit more complicated.

Setting up ASP.NET Identity in the CMS project is documented on EPiServer World.

Setting up Service API starts with changing authentication from membership provider to ASP.NET Identity. To do that, you have to implement your own OAuthAuthorizationServerProvider.

public class IdentityAuthorizationProvider : OAuthAuthorizationServerProvider
{
  public override Task ValidateClientAuthentication(
                            OAuthValidateClientAuthenticationContext context)
  {
     context.Validated();
    return Task.FromResult(0);
  }

  public override async Task GrantResourceOwnerCredentials(
                            OAuthGrantResourceOwnerCredentialsContext context)
  {
    var signInManager =
          ServiceLocator.Current.GetInstance<SignInManager<ApplicationUser, string>>();
    var result = await signInManager.PasswordSignInAsync(
                                         context.UserName,
                                         context.Password,
                                         isPersistent: false,
                                         shouldLockout: false);
    if (result == SignInStatus.Success)
    {
      var identity = new ClaimsIdentity(context.Options.AuthenticationType);
      var principal = PrincipalInfo.CreatePrincipal(context.UserName);
      if (principal is GenericPrincipal)
      {
        var generic = principal as GenericPrincipal;
        identity.AddClaims(generic.Claims);
      }

      context.Validated(identity);
    }
    else
    {
      context.Rejected();
    }
  }
}

You have to implement two methods - ValidateClientAuthentication and GrantResourceOwnerCredentials. Authentication is implemented in the GrantResourceOwnerCredentials method and it just uses ASP.NET Identity's SignInManger for your application user to sign in with a password. You can retrieve username and password for signing in from the context. Username and password are populated with values from Service API authentication request. Then if the sign in is successful, create claims identity and call a Validated method on the context. If the sign in was not successful, call Rejected.

Next step is registering IdentityAuthorizationProvider in the StructureMap container:

For<IOAuthAuthorizationServerProvider>().Use<IdentityAuthorizationProvider>();

The last step is checking that EPiServer ASP.NET Identity's OWIN Startup configuration is called the last - after your authentication configuration calls in the Startup class.

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.AddCmsAspNetIdentity<ApplicationUser>();

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Util/Login.aspx"),
            Provider = new CookieAuthenticationProvider
            {
             OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager<ApplicationUser>, ApplicationUser>(
               validateInterval: TimeSpan.FromMinutes(30),
               regenerateIdentity: (manager, user) => manager.GenerateUserIdentityAsync(user))
            }
        });

        new EPiServer.ServiceApi.Startup().Configuration(app);
    }
}

If you call EPiServer ASP.NET Identity's OWIN Startup configuration before app.AddCmsAspNetIdentity() call, then SignInManger will not be available in the IdentityAuthorizationProvider and you will get an exception. app.AddCmsAspNetIdentity() registers all required services for authentication (including SignInManager) in the OWIN context.

Summary

It is really nice that EPiServer created a package for ASP.NET Identity but it is missing some parts. For example, it could have OAuthAuthorizationServerProvider implementation available and also all the registration set up.