IdentityServer4 Essentials

I am currently working on a personal project and have decided - as a learning exercise - to set up and configure IdentityServer4. This guide is what I wished I had before starting. It will cover some of the basics surrounding OAuth and Open ID Connect before diving into IdentityServer4.

Layout of the landscape

Probably the most confusing aspect of IdentityServer4 (IS4) is the terminology. Here is a high-level overview of the main terms:

Authentication = are you really who you say you are? Sounds obvious, but not to be confused with authorization…

Authorization = what should you be allowed to do? Can you read/write or Read-only?

OAuth (1.0 / 2.0) = a protocol enabling you to sign in using your existing account for another service. OAuth has a number of “flows” (ways to log in and communicate with the app asking for permissions). The end result is giving the requesting application a set of claims (a simple key/value list of attributes). Standard claims are userid, email, and name. It can include things like birthday, customer id, or anything else. OAuth is primarily used for authorization - it assumes the user has already been authenticated elsewhere.

OpenID = another standard protocol for authenticating to external apps from a single account but focused only on authentication. This is the key difference with OAuth, where it was concerned on authorization, but it lacked the ability to authenticate users (although it didn’t stop companies writing their own authentification layer for OAuth).

OpenID Connect (OIDC) = modern protocol that merges the latest OAuth and OpenID into a single system that lets an app know who you are and also optionally ask for authorization/permissions data if available. This is a direct extension over OAuth adding an additional layer to provide a mechanism to authenticate.

You can create your own scopes, for example, here are some custom scopes that github provides:

IdentityServer4 Internals

As an example, let’s imagine we have a Mobile app that requires authentication. We want a simple login page with username and password textbox and a login button, which then will enable the app to authenticate the user and authorize the client to access an API to request some data. Let’s start with the client:

Above we define a client, MobileAppClient with the ResourceOwnerPassword OAuth grant type.
This particular flow enables our app (the client) to authenticate on behalf of our user (i.e. the user gives his username and password, and the client will then pass those details on and request a token). This particular client has a scoped access to read-only, so they can only read from our API.

Resources (they were called scopes in IS3)

Next, we define what resources/scopes we want to protect with IdentityServer. In other words, what resources is the client allowed to use? e.g.

IdentityResources are things like user ID, name and email address of the user. We can assign it a list of scopes to this, and the IdentityResource will then include these scopes. The minimum implementation is to assign a unique ID to users, also called subject ID, which we do by exposing the OpenID scope. We could also assign Email and Profile scope. For the sake of learning, we are also defining a custom scope, role which returns the role claims for the authenticated user. The OpenID scope returns a Subject claim, see here.

To allow a client to access our API we create an API resource. We are effectively modelling the API that we wish to protect. To enable a client to get an access token to query an API, the client must select a scope. The resource for the API has two scopes that a client is allowed to access: customAPI.read and customAPI.write. In the above example, we provide clients with two scopes.

Claims

Claims are specific attributes about a user. They are key/value pairs containing info about a user, as well as meta-description about OIDC service.

Wiki defines it as “A claim is a statement that one subject, such as a person or organization, makes about itself or another subject. For example, the statement can be about a name, group, buying preference, ethnicity, privilege, association or capability.”.

To provide an example, in your application your user may have a name, email and company name - these are your claims. If a client requests access to the Admin controller of your Web API, this would be a scope. Note, that a claim is defined as part of the resource.

This will enable the user, ben, to authenticate and request a token. To persist this, in IS4 we would then need to use the IResourceOwnerPasswordValidator to authenticate the user, and then use IProfileService to get the claims.

Tokens

Now that we know the basics about what a user, a client and resources (including scopes and claims) we can now discuss tokens. There are three types of tokens in OIDC: id_token, access_token and refresh_token.

id_token

Id_Token is used only in an implicit or Hybrid flow which is outside the scope of this post. But essentially, it stores information about the user (encoded within the token aka claims). Many OIDC implementers will use JSON Web Token (JWT) as the format for this token - but this is not part of the OIDC spec. JWT is also commonly used for access_token and refresh_token.

access_token

Access_token are used as bearer tokens. A bearer token means that the bearer can access the authorized resource without further authentication (identification). These tokens must be protected as if someone got access to your token they can do anything (within the scope enabled). By looking at your claims, they could figure out quickly how much they can do. The claims store information about the client and the user - this is how the APIs authorize (not to be confused with authenticating) access to their data. Access_tokens generally have a short lifespan. For clients that require a longer life-span, they would typically use long-lived refresh tokens to refresh their access_token.

refresh_tokens

Refresh_tokens are used to obtain new access_tokens. Typically, refresh tokens are long-lived, whereas access_tokens are short lived. Here is the flow:

User requests access and refresh token

once access token expired, use the refresh token to get the new token

repeat until refresh token expires

when refresh token expires, the user will need to re-authenticate

This is extremely useful for a scenario where an admin person wants to revoke access to a user. They can revoke the subscription token and block their account so when the user tries to refresh and they need to reauthenticate, which they won’t be able to.

Refresh tokens can be enabled in IS4 by specifying if the client enables offline access i.e. to disable:

newClient{...// Set to false to disable refresh TokensOfflineAccess=false,...}

It can be confusing sometimes to distinguish between the different token types. Here’s a quick reference:

ID tokens carry identity information encoded in the token itself, which must be a JWT

Access tokens are used to gain access to resources by using them as bearer tokens

Refresh tokens exist solely to get more access tokens

There we have it, a short introduction on how things are set out. Here are a bunch of resources that I would highly recommend you go through before doing anything with IS4 as it will save you a lot of time in the long run.