Blog Post

Identity Server 4

Monday, October 30, 2017 12:41 AM

Identity Server 4 is the newest iteration of IdentityServer, the popular OpenID Connect and OAuth Framework for .NET, updated and redesigned for ASP.NET Core and .NET Core. In this article we are take a quick look at why IdentityServer 4 exists, and then dive right in and create ourselves a working implementation from zero to hero.

IdentityServer 3 vs IdentityServer 4

A popular phrase going at the moment is 'conceptually compatible' but this rings true for Identity Server 4. The concepts are the same, it is still an OpenID Connect provider built to spec, however most of its internals and extensibility points have changed. When we integrate a client application with IdentityServer, we are not integrating to an implementation. Instead we are integrating using the OpenID Connect or OAuth specifications. This means any application that currently works with IdentityServer 3 will work with IdentityServer 4.

Identity Server is designed to run as a self-hosted component, which was difficult to achieve with ASP.NET 4.x with MVC still being tightly coupled to IIS, and System.Web, resulting in an internal view engine served up by the katana component. With Identity Server 4 running on ASP.NET Core, we can now use any UI technology and host IdentityServer in any environment ASP.NET Core can run in. This also means we can now integrate with existing login forms/systems, allowing for in place upgrades.

The Identity ServerIUserServicethat was used to integrate your user store is also gone now, replaced with a new user store abstraction in the form ofIProfileServiceandIResourceOwnerPasswordValidator.

IdentityServer 3 isn’t going anywhere, just as the .NET Framework isn’t going anywhere. Just as Microsoft has shifted most active development to .NET Core (seeKatanaandASP.NET Identity), I imagine IdentityServer will eventually do the same, but we are talking about OSS here and whilst the project stays that way it will always be open to PRs for bug fixes and relevant new features. I for one won’t be abandoning it any time soon and commercial support will continue.

At the initial time of writing IdentityServer 4 was in RC5 and IdentityServer 3 was at v2.5.3 with another major release (v3.0.0) planned for the future. This article has since been updated to IdentityServer 4 v1.5.

IdentityServer4 targets .NET standard 1.4, meaning it can target either .NET core or the .NET framework, although this article will target .NET Core only. IdentityServer 4 nowsupports .NET Core 1.1, leaving behind .NET Core 1.0 due to breaking changes between the two versions.

Implementing IdentityServer4 on ASP.NET Core and .NET Core

For our initial implementation we’ll use the In-Memory services reserved for demos and lightweight implementations. Later in the article we will switch to entity framework for a more realistic representation of a production instance of IdentityServer.

Before starting this tutorial, please ensure you are using the latest version of ASP.NET Core and the .NET Core tooling When creating this tutorial I used .NET Core 1.1 and Visual Studio 2017.

To start with we’ll need a new ASP.NET Core project that uses .NET Core (inVSsee 'ASP.NET Core Web Application (.NET Core)'). You’ll want to use the Empty template with no authentication.

Before we start coding, switch the project URL to HTTPS. There’s no scenario where you should be running an authentication service withoutTLS. Assuming you are using IIS Express, you can do this by opening up the properties of your project, entering the Debug tab and clicking 'Enable SSL'. Whilst we are here, you should make the generated HTTPS URL your App URL, so that when we run the project we start off on the right page.

If you experience certificate trust issues when using the IIS Express development certificate for localhost, try following thesteps in this article. If you find issues with this approach, feel free to switch to self-hosted mode (instead of IIS Express, run using your project's namespace).

To start we need to install the following nuget package (article currently written for 1.5.0):

IdentityServer4

Now to our Startup class to start registering dependencies and wiring up services.

In yourConfigureServicesmethod add the following to register the minimum required dependencies:

And then in yourConfiguremethod add the following to add the IdentityServer middleware to the HTTP pipeline:

app.UseIdentityServer();

What we have done here is registered IdentityServer in our DI container usingAddIdentityServer, used a demo signing certificate withAddTemporarySigningCredential, and used in-memory stores for our clients, resources and users. By usingAddIdentityServerwe are also causing all generated tokens/grants to be stored in memory. We will add actual clients, resources and users shortly.

We can actually run IdentityServer already, it might have no UI, not support any scopes and have no users, but you can already start using it! Check out the OpenID Connect Discovery Document at/.well-known/openid-configuration.

OpenID Connect Discovery Document

The OpenID Connect Discovery Document is available on every OpenID Connect provider at this well known endpoint (as per the spec). This document contains information such as the location of various endpoints (e.g. the token endpoint and the end session endpoint), the grant types the provider supports, the scopes it can provide, and so on. By having this standardised document, we open up the possibility of automatic integration.

Signing Certificate

A signing certificate is a dedicated certificate used to sign tokens, allowing for client applications to verify that the contents of the token have not been altered in transit. This involves a private key used to sign the token and a public key to verify the signature. This public key is accessible to client applications via thejwks_uriin the OpenID Connect discovery document.

When you go to create and use your own signing certificate, feel free to use a self-signed certificate. This certificate does not need to be issued by a trusted certificate authority.

Now that we have IdentityServer up and running let's add some data to it.

Clients, Resources and Users

First we need to have a store of Client applications that are allowed to use IdentityServer, as well the Resources that these clients can use and the Users that allowed to authenticate on them.

We are currently using the InMemory stores and these stores accept a collection of their respective entities, which we can now populate using some static methods.

Clients

IdentityServer needs to know what client applications are allowed to use it. I like to think of this as a whitelist, your Access Control List. Each client application is then configured to only be allowed to do certain things, for instance they can only ask for tokens to be returned to certain URLs, or they can only request certain information. They have scoped access.

Here we are adding a client that uses the Client Credentials OAuth grant type. This grant type requires a client Id and client secret to authorize access, with the secret being hashed using an extension method provided by Identity Server (we never store any passwords in plain text after all). The allowed scopes is a list of scopes that this client is allowed to request. Here our scope is customAPI.read, which we will initialize now in the form of an API resource.

Resources & Scopes

Scopes represent what you are allowed to do. They represent the scoped access I mentioned before. In IdentityServer 4 scopes are modelled as resources, which come in two flavors: Identity and API. An identity resource allows you to model a scope that will return a certain set of claims, whilst an API resource scope allows you to model access to a protected resource (typically an API).

IdentityResources

The first three identity resources represent some standard OpenID Connect defined scopes we wish IdentityServer to support. For example heemailscope allows theemailandemail_verifiedclaims to be returned. We are also creating a custom identity resource in the form ofrolewhich returns anroleclaims for authenticated user.

A quick tip, theopenidscope is always required when using OpenID Connect flows. You can find more information about these in theOpenID Connect Specification.

ApiResources

For api resources we are modelling a single API that we wish to protect calledcustomApi. This API has two scopes that can be requested:customAPI.readandcustomAPI.write.

By setting claims within the scope like this we are ensuring that these claim types will be added to any tokens that have this scope (if the user has a value for that type, of course). In this case we are ensuring that a users role claims will be added to any tokens with this scope. The scope secret will be used later during token introspection.

Scope vs Resource

OpenID Connect and OAuth scopes now being modelled as resources, is the biggest conceptual change between IdentityServer 3 and IdentityServer 4.

Theoffline_accessscope, used to request refresh tokens, is now supported by default with authorization to use this scope controlled by theClientpropertyAllowOfflineAccess.

Users

In the place of a fully fledged User Store such as ASP.NET Identity, we can use TestUsers:

A users subject (or sub) claim is their unique identifier. This should be something unique to your identity provider, not something like an email address. I point this out due to arecent vulnerability with Azure AD.

We now need to update our DI container with this information (instead of the previous empty collections):

We can now use the token introspection endpoint of IdentityServer to validate the token, as if we were an OAuth resource receiving it from an external party. If successful, we’ll receive the claims in that token echoed back to us. Note that the access token validation endpoint from IdentityServer 3 is no longer available in IdentityServer 4.

It is here that the scope secret we created earlier comes into use, by using Basic Authentication where the username is the scope Id and the password a scope secret.

Where the redirect and post logout redirect uris are the url of our upcoming application. The redirect uri requires the path/signin-oidcand this path will be automatically created and handled by an upcoming piece of middleware.

Here we are using the OpenID Connect implicit grant type. This grant type allows us to request identity and access tokens via the browser. I would call this the simplest grant type to get started with.

Client Application

Now we need to create the client application. For this we’ll need another ASP.NET Core website, this time using the Web Application VS template but again, with no authentication.

To add OpenID Connect authentication to a ASP.NET Core site we need to add the following two packages to our site:

Here we are telling our application to use cookie authentication, for signing in users. Whilst we may be using IdentityServer to log in users, every application still needs to issue its own cookie (to its own domain).

Here we are telling our app to use our OpenID Connect Provider (IdentityServer), the client id we wish to sign in with and the authentication type to login with upon successful authentication (our previously defined cookie middleware).

Now all that’s left is to make a page require authentication to access. Let’s add the authorize attribute to the Contact action, because people contacting us is the last thing we want.

[Authorize]public IActionResult Contact(){...}

Now when we run this application and select the Contact page, we’ll receive a 401 unauthorized. This in turn will be intercepted by our OpenID Connect middleware, which will 302 redirect us to our Identity Server authentication endpoint along with the necessary parameters.

IdentityServer 4 Quickstart UI Login Screen

Upon successful login, IdentityServer will then ask our consent for the client application to access certain information or resources on your behalf (these correspond to the identity and resource scopes the client has requested). This consent request can be disabled on a client by client basis. By default the OpenID Connect middleware for ASP.NET Core will request the openid and profile scopes.

IdentityServer 4 Quickstart UI Consent Screen

And that’s all that’s required for wiring up a simple OpenID Connect Client using the implicit grant type.

Entity Framework Core

Currently we are using in memory stores, which as we noted before is for demo purposes or, at most, very lightweight implementations. Ideally we’d want to move our various stores into a persistent database that won't be wiped on every deploy or require a code change to add a new entry.

IdentityServer has anEntity Framework (EF) Core packagethat we can use to implement client, scope and persisted grant stores using any EF Core relational database provider.

The Identity Server Entity Framework Core package has been integration tested using the In-Memory, SQLite (in-memory) and SQL Server database providers. If you find any issues with other providers or wish to write tests against other database providers, feel free to open up an issue on the GitHub issue tracker or submit a pull request).

For this article we will be using SQL server and either SQL Express or Local DB, so we’ll require the following nuget packages:

Persisted grant store

The persisted grant store contains all information regarding given consent (so we don't keep asking for consent on every request), reference tokens (stored jwt’s where only a key corresponding to the jwt is given to the requester, making them easily revocable), and much more. Without a persistent store for this, tokens will be invalidated on every redeploy of IdentityServer and we wouldn't be able to host more than one installation at a time (no load balancing).

Where our migrations assembly is our project hosting IdentityServer. This is necessary to target DbContexts not located in your hosting project (in this case it is in the nuget package) and allows us to run EF migrations. Otherwise we’ll be met with an exception with a message such as:

Currently we need to create ourselves a custom implementation ofIdentityDbContextin order to override the constructor to take a non-generic version ofDbContextOptions. This is becauseIdentityDbContextonly has a constructor accepting the genericDbContextOptionswhich, when we are registering multipleDbContexts, results in an Invalid Operation Exception. I’ve openedan issue on this, so hopefully we can skip this step soon.

That’s all that’s needed to wire up ASP.NET Core Identity with IdentityServer 4, but unfortunately our Quickstart UI we downloaded earlier is no longer going to work properly, as it is still using aTestUserStore.

However, we can modify our existing AccountsController from the Quickstart UI to work for ASP.NET Core Identity by replacing some code.

First we need to change the constructor to accept the ASP.NET Core IdentityUserManager, instead of the existingTestUserStore. Our constructor should now look like this: