OAuth2 Walkthrough using Identity Server and ASP.NET

Creating an identity server using Aspnet Identity and Entity Framework storage

Index

Creating an identity server using Aspnet Identity and Entity Framework storage
Extending Identity Server with Identity Manager (pre-release)
Configuring and loading client and scope data with the CLI
Creating an MVC Client using Resource Authorization

Introduction

This series aims to provide a practical walk through of a production ready setup of IdentityServer 3 and different .net clients (mvc, webApi and SPA's). It does not cover the OAuth2 or OpenID Connect specifications, so some prior know of these is assumed. The walk-through leverages a number of samples provided by Thinktecture, building out a solution that can be easily migrated to production

Thinktecture already have many walk-throughs and samples, the aim here is not to reproduce these, but see how they all tie together. For a detailed explanation, these samples are a great resource

Getting Started: Creating the simplest OAuth2 Authorization Server, Client and API
Getting Started: MVC Authentication & Web APIs

There's a lot going on with OAuth and OpenId Connect, IdentityServer combines the 2 specifications into a single server application, allowing a client to authenticate using the OpenId endpoints (for certain flows), and obtain authorisation for a specific resource. Before getting started, here's some common terminology when working with Identity Server.

Terminology

Resource Owners

In OAuth terms, the Resouce Owner can be thought of as the end user.

Clients

The clients are the consumers of the identity and authorisation endpoints. Theses are typical browser based or native applications and are categorised into public or confidential clients

Public clients

Public clients incapable of maintaining the confidentiality of their credentials (e.g., clients executing on the device used by the resource owner, such as an installed native application or a web browser-based application), and incapable of secure client authentication via any other means

Confidential clients

Confidential Clients capable of maintaining the confidentiality of their credentials (e.g., client implemented on a secure server with restricted access to the client credentials), or capable of secure client authentication using other means.

Flows / Grant Types

The flows (or grant types) a the different authorisation mechanisms for the given client. Care must be taken to ensure the correct flow is selected for the given client type, public or confidential. Care must be taken when selecting a flow type for a client, selecting flow type which is not intended for a client type can expose your client by being insecure

Authorisation Code flow

  1. Optimized for confidential clients
  2. Used to obtain both access tokens and refresh tokens
  3. Clients authenticated using client-id and client-secret
specification

Implict flow

  1. Optimised for public clients (browser)
  2. Single request for authorisation and access token
  3. Does not include client authentication
  4. Does not suport refresh tokens
specification

Resource Owner Password Credentials flow

  1. For use where the resource owner has a trust relationship with the client
  2. suitable for clients capable of obtaining the resource owner's credentials (username and password, typically using an interactive form)
  3. The authorization server should take special care when enabling this grant type and only allow it when other flows are not viable.
specification

Client Credentials flow

  1. MUST only be used by confidential clients
  2. Clients authenticated using client-id and client-secret
specification

Hybrid flow

  1. Combination of Authorisation Code and Implicit flows
  2. Returns Identity and access tokens in a single request
  3. Suports refresh tokens

Flow Summary

The table below surmises the different flow types available

Flow Type Client Type Identity Tokens Refresh Tokens
Authorisation Code flow confidential / public 1 no yes
Implict flow public yes no
Resource Owner Password Credentials flow confidential no yes
Hybrid flow public yes yes

1 Optimised for confidential clients

Creating the solution and installing Identity Server

Installing Identity Server (Nuget)

Lets get our hands dirty, when dealing with any security based applications SSL should always be used. Setting up the project...

  1. Create an Empty MVC project called OAuth2Demo.IdentityServer, naming the solution OAuth2Demo
  2. Hit F4 on the project, setting its SSL Enabled to true
    SSL enbled for MVC Project
  3. Hit Alt + Enter on the project, updating the project URL to the SSL URL on the web tab
    MVC Project Properties
  4. Using Nuget Package manager install the following packages
    install-package Microsoft.Owin.Host.SystemWeb
    install-package IdentityServer3
  5. Add an Owin Startup class to the App_Start folder
    Add Owin Startup Class
  6. Stub out the following code in the startup class
        public class Startup {
            public void Configuration(IAppBuilder app) {
    
                app.Map("/identity", id => {
                    id.UseIdentityServer(new IdentityServerOptions {
                        SiteName = "Demo Identity Server",
                        SigningCertificate = LoadCertificate()
                        
                        // Factory =  TODO
                    });
    
                });
    
            }
    
            X509Certificate2 LoadCertificate() {
    
                //Test certificate sourced from https://github.com/IdentityServer/IdentityServer3.Samples/tree/master/source/Certificates
                return new X509Certificate2(
                    string.Format(@"{0}\bin\{1}", AppDomain.CurrentDomain.BaseDirectory, ConfigurationManager.AppSettings["signing-certificate.name"]),
                    (string)ConfigurationManager.AppSettings["signing-certificate.password"]);
            }
        }
    
    This code injects the Identity Server application, configuring
    1. The site name
    2. The signing certificate which is used to sign the tokens (note: this techincally can be the SSL certificate, but is usually different). Thinktecture provide a test certificate that can be downloaded here
  7. Enable RAMFAR within the web.config
      <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
      </system.webServer>
    
  8. The site will not start yet as we have to configure the IdentityServerServiceFactory, for this we are going to use the Aspnet Identity model and Entity Framework as the data acess

    Adding ASP Identity and Entity Framework Services

    1. Using Nuget Package Manger, install the following packages

      install-package Microsoft.AspNet.Identity.EntityFramework
      install-package IdentityServer3.AspNetIdentity
      install-package IdentityServer3.EntityFramework
      
    2. Add a Entities.cs file to the solution, pasting in the following code

      using Microsoft.AspNet.Identity;
      using Microsoft.AspNet.Identity.EntityFramework;
      
      namespace OAuth2Demo.IdentityServer {
      
          public class Context : IdentityDbContext<IdentityUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>{
              public Context(string connectionString) : base(connectionString) { }
          }
      
          public class UserStore : UserStoret<IdentityUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim> {
              public UserStore(Context context) : base(context) { }
          }
      
          public class RoleStore : RoleStoret<IdentityRole> {
              public RoleStore(Context context) : base(context) { }
          }
      
      
          public class UserManager : UserManagert<IdentityUser, string> {
              public UserManager(UserStore userStore)
                  : base(userStore) {
              }
          }
      
          public class RoleManager : RoleManagert<IdentityRole> {
              public RoleManager(RoleStore roleStore) : base(roleStore) { }
          }
      }
      

      This code cofigures

      1. The Entity Framework context
      2. The User and Role stores
      3. The User and Role managers

      Using the default Entity Framework Identity models. These models can be extended as required, as the whole ASP.NET Identity framework is extendable

    3. Create a Services folder, adding the class IdentityUserService.cs. Extend the default aspnet identity service as per below (this is required for the dependency injection to work)

      using IdentityServer3.AspNetIdentity;
      using Microsoft.AspNet.Identity.EntityFramework;
      
      namespace OAuth2Demo.IdentityServer.Services {
          public class IdentityUserService : AspNetIdentityUserService<IdentityUser, string> {
      
              public IdentityUserService(UserManager userManager) : base(userManager) {
              }
          }
      }
      

    Extending the Service Factory

    1. Add an Extensions folder to the project, adding the class IdentityServerServiceFactoryExtensions.cs. Add the extension method below

      using IdentityServer3.Core.Services;
      using IdentityServer3.EntityFramework;
      using OAuth2Demo.IdentityServer;
      using OAuth2Demo.IdentityServer.Services;
      
      namespace IdentityServer3.Core.Configuration {
          public static class IdentityServerServiceFactoryExtensions {
              public static IdentityServerServiceFactory Configure(this IdentityServerServiceFactory factory, string connectionString) {
      
                  var serviceOptions = new EntityFrameworkServiceOptions { ConnectionString = connectionString };
                  factory.RegisterOperationalServices(serviceOptions);
                  factory.RegisterConfigurationServices(serviceOptions);
      
                  factory.Register(new Registration<Context>(resolver => new Context(connectionString)));
                  factory.Register(new Registration<UserStore>());
                  factory.Register(new Registration<UserManager>());
                  factory.UserService = new Registration<IUserService, IdentityUserService>();
      
                  return factory;
      
              }
          }
      }
      

      These extension methods register the stores and the mangers with the dependency injection container and wire up the services. The methods RegisterOperationalServices and RegisterConfigurationServices. These 2 methods register the stores required for operational (AuthorizationCodeStore, TokenHandleStore, ConsentStore, RefreshTokenStore) and configuration (ClientStore, ScopeStore) services

    Refactoring the Startup class

    1. Refactor the start up class to include new IdentityServerServiceFactory
              public void Configuration(IAppBuilder app) {
                  string connectionString = ConfigurationManager.ConnectionStrings["cnn"].ConnectionString;
      
                  app.Map("/identity", id => {
                      id.UseIdentityServer(new IdentityServerOptions {
                          SiteName = "Demo Identity Server",
                          SigningCertificate = LoadCertificate()
      
                          Factory = new IdentityServerServiceFactory().Configure(connectionString),
                      });
      
                  });
              }
      

    Spin it up...

    Compile and run the project, navigating to /Identity and you should be presented with the screen below..

    Identity Server home page

    Source Code

    git clone https://github.com/mindfulsoftware/oauth2Demo.git