PASS Data Community Summit logo

PASS Data Community Summit 2023

Get all the latest announcements direct to your inbox

Working with Identity Server 4

Identity Server is a popular authentication framework for .NET, and version 4 was built for ASP.NET Core. In this article, Camilo Reyes explains Identity Server 4 and how to get started working with it.

Identity Server 4 is the tool of choice for getting bearer JSON web tokens (JWT) in .NET. The tool comes in a NuGet package that can fit in any ASP.NET project. Identity Server 4 is an implementation of the OAuth 2.0 spec and supports standard flows. The library is extensible to support parts of the spec that are still in draft.

Bearer JWT tokens are preferable to authenticate requests with a backend API. The JWT is stateless and aids in decoupling software modules. The JWT itself is not tied to the user session and works well in a distributed system. This reduces friction between modules since it does not share dependencies like a user session.

In this take, I’ll delve deep into Identity Server 4. This OAuth implementation is fully compatible with the spec. I’ll start from scratch with an ASP.NET Web API project using .Net Core. I’ll stick to the recommended version of .NET Core, which is 3.0.100 at the time of this writing. You can find a working sample of the code here.

To begin, I’ll use CLI tools to keep the focus on the code without visual aids from Visual Studio. To fire up an ASP.NET Web API project, create a new project folder, change directory into it, and do:

The tooling should scaffold a project you can run. Add Identity Server 4 as a dependency:

Doing this from Visual Studio works too if that is preferred. With this, I am ready to begin the integration of Identity Server 4 into this project. In the code samples, I’ll ignore using statements unless necessary to put more focus on the integration itself.

OAuth Token Grant Type Flows

Identity Server 4 supports flows such as authorization code with hybrid and implicit grant types. It supports device code for use cases that lack a browser. For this tutorial, I’ll focus on the most useful flows to protect resources:

  • Client Credentials: When the client application is acting on its own behalf. Think of it as robots talking to other robots.
  • Resource Owner Password Credentials: Exchange user credentials such username and password for an access token. The token uniquely identifies a person requesting access to protected resources. Think of it as an identity card you carry around to gain privileged access.
  • Refresh Token: Request a new access token when the current access token becomes invalid or expires. Think of it as a long-lived token, and a way to renew access.

The use case is a person can log in with valid credentials to get tokens. As the access token expires, they can request new tokens with the refresh token. For applications where no one is driving the request, a client credential token can gain access.

Identity Server 4 Client Configuration

To get Identity Server 4 up off the ground, begin with client configuration. In OAuth lingo, a client is the uniquely identifiable app making token requests. Each client can set up allowed grant types and client scopes. These two decide which tokens the client can get from the identity provider. The identity provider is the authentication authority for generating and validating tokens.

Declare a ClientStore class with the following implementation:

I’ll revisit GetClients as I flesh out client configuration for each grant type. There are scopes for client credential tokens in ApiResource. For resource owner tokens, it needs scopes in IdentityResource. Allowed scopes must appear here first before any one client can use them. Identity Server 4 supports client configuration from a back-end database. It comes with Entity Framework as the data access layer. For this project, I’ll stick to in-memory client configuration. The focus is on generating tokens.

Client registration goes in configuration that adds Identity Server 4 to this project. Open the Startup class in a code editor and then:

I’m using a temporary signing credential to sign JWTs coming from this identity provider. Passing in false makes it to where the key does not persist on disk. This means it changes every time the app boots up. Identity Server 4 offers asymmetric RSA keys for local development. Asymmetric means there two separate keys. One private key to sign JWTs coming from the identity provider. One public so client apps can validate JWTs and check that they come from the right authority.

Token Request/Response

The Client Credential flow has the following request type:

  • grant_type: This must be set to client_credential
  • client_id: Uniquely identifies the client requesting tokens
  • client_secret: Secret password only known to the client making the request
  • scope: List of requested scopes that will go in the JWT to access protected resources

The Resource Owner Password Credential flow has the following request type:

  • grant_type: This must be set to password
  • username: The person’s username credential
  • password: The person’s password credential
  • client_id: The target client app they’re login into
  • client_secret: The target client’s secret
  • scope: Must be set to openid to request the access token. To get a refresh token, add offline_access.

And finally, the Refresh Token flow has the following request type:

  • grant_type: This must be set to refresh_token
  • client_id: The client app id where the access token came from
  • client_secret: The client app secret, which comes from the client app itself
  • refresh_token: The original refresh token that comes with the access token

Because all these flows have a lot in common, I’ll reduce this down to a single C# type. This frees me from having to repeat myself with all the different grant types.

For example:

I opted to get request values from a form POST request because Identity Server 4 reads client id/secret data from a form content type. The client id/secret are not part of this TokenRequest since it reads it off the raw request.

All grant type flows have the following response:

  • access_token: A valid JWT. Comes with a sub claim for tokens that identify a person.
  • refresh_token: Optional token for renewing the access token
  • token_type: Must be set to “Bearer”
  • expires_in: Token lifetime set in seconds
  • error: Error code in case of a Bad Request response
  • error_description: Optional descriptive message of the error code

In plain C#, this looks like this:

Use a Newtonsoft attribute to format the JSON content-type response. Note DefaultValueHandling is set to ignore to allow as many token responses without clobbering the response.

Token Provider

It’s time to generate tokens in Identity Server 4. I’ll abstract this with an ITokenProvider interface:

One caveat is to avoid adding too many layers of indirection in front of Identity Server 4. At some point, the code might roll out its own security or reinvent the wheel. Here, I’m only adding this token provider and letting the library do the hard work.

Create a TokenProvider class that implements ITokenProvider with the following dependencies:

These dependencies have the following using statements:

Because Identity Server 4 works well with .NET Core, it knows how to get dependencies. Be sure to add them to the constructor and the library will do the rest of the job. This comes from service configuration in Startup when it calls AddIdentityServer. This is all thanks to .NET Core by putting dependency injection front and center. Dependencies are mockable, so it’s easy to write unit tests.

To implement the interface, do:

Parameters are pass-through values since it already knows about the different grant types. I’m picking values straight from TokenRequest and placing it in a NameValueCollection. The response_type says which kind of token it gets in the response. I’m setting this to Token because I want an access token.

The private method GetIdpToken gets a TokenResponse from Identity Server 4. Because the name clashes with our own token response, set a using alias:

Then, declare the private method as:

This does two things, validate client id/secret and token request, and generate tokens. Client id/secret data comes from HttpContext because it reads it off the raw request. Errors get placed in a custom dictionary for later retrieval.

With the IdpTokenResponse set, do the mapping to the TokenResponse:

As mentioned, the TokenType must be set to a bearer type. This lets consuming apps know it’s meant as a bearer token. If there are any errors, set the Error and ErrorDescription property when available.

This completes token generation so go ahead and close this file. Note I’m writing pass-through code and letting Identity Server 4 do its work.

Token Endpoint

Add a TokenController with the following dependency:

Because it needs to know how to inject this dependency, put this in the Startup class:

For now, the controller class should look like this:

This means the endpoint will be /token to make token requests. In the TokenController put in place the form POST request:

The FromForm attribute makes it so this grabs the TokenRequest from a form POST. Note the quick check for an error and the 400 Bad Request response. The spec says it must be set to 400. It may respond with a 401 Unauthorized when client id/secret validation fails. And, it must respond with a 401 when client id/secret fails and is sent via the header. Because I’m sending client data through a form post, I’m keeping this simple.

Since Newtonsoft formats the JSON response, be sure to add support for this:

Then register it:

Also, I’m going to disable HTTPS redirection that comes with the project. I don’t want to have to deal with local certificates and whatnot. So, find this in Startup and remove this from the project:

This allows local dev tools like curl.exe which don’t support HTTPS redirection to work. One caveat is the spec does say the token endpoint must require TLS encryption. This is to avoid sending credentials in cleartext. Keep this in mind when the project is ready to ship.

With the token endpoint ready to serve requests. It’s time to start generating tokens. It’d be good to see each grant type sending back tokens.

Client Credential Token

The good news is most of the code is already in place. To finish this, it needs a client configuration that allows client credential tokens. Open up ClientStore, make sure it’s in the GetClients method where it returns null and replace it with this:

The client secret itself gets hashed in the client store. Identity Server 4 treats client secrets like a password, so it must be hashed. Storing passwords in plain text will not work, so note the call to Sha256. The AllowedGrantTypes is set to the flow it can support. This means this client can only respond with client credential tokens. Each client configuration must have at least one scope. I’m using “all” as a default scope to indicate a catch-all scope. Setting AllowOfflineAccess to false means this client does not support refresh tokens. Identity Server 4 does not allow refresh tokens in the client credentials flow. Client credential tokens are suitable for one-time use with a short lifetime.

Go ahead and fire up this project with dotnet watch run. I like running .Net Core projects in watch mode, so it refreshes automatically. This means every time there’s a code change, it rebuilds and runs automatically.

You can use Postman to send requests to the endpoint, which is the tool I recommend. Because I don’t want to hit you with a bunch of fuzzy images from Postman, I’m using curl.exe. This CLI tool ships with the latest public release of Windows 10.

To get client credential tokens from this endpoint, do:

Note all the required parameters to get client cred tokens are there. This curl command needs to go in a single line. Everything in the double quotes shouldn’t have any spaces.

If you don’t see a response, check that there isn’t any HTTPS redirection. Curl has an

--include flag that shows response headers. The default project template responds with a 307 Temporary Redirect response which is not supported.

This is what the response looks like:

Resource Owner Token

The resource owner token flow has the following client configuration:

The AllowedScopes must be set to OpenId. If it needs to support refresh tokens, add the OfflineAccess scope. Token lifetime expirations are set in seconds.

This token flow is not quite ready yet. Requesting tokens throws an error saying it needs IResourceOwnerPasswordValidator. This interface is straightforward, and it’s what validates user credentials.

Put in place this interface like this:

Then, add it to Startup:

The code above needs the following using statement:

I’m using a poor man’s credential validator to short-circuiting the logic when it fails. In a real project, be sure to check against a stored hash in a database. If the credentials are valid, set IsError to false and set the Subject. The Subject includes the subject or “sub” claim for this JWT. This is what ties the JWT to a living person. Here I’m sticking a random Guid, but it can be the user id from the database. Identity Server 4 requires all these three claims in the generated JWT.

Restart the .Net Core watcher, so it picks up the new file. Then run curl to get tokens:

The token response looks like this:

Going to jwt.io and pasting the access token shows the following:

In this JWT, the exp, client id, and sub are of interest. The exp stands for expiration in Unix epoch time. The client id is the originating app, and sub is the person’s identity.

Refresh Token

To get a refresh token, it needs the refresh token that comes with the resource owner token response. Do this to get a new access token:

In Identity Server 4 the refresh token can expire. There are options for when the refresh token expires. In this case, the client is set to absolute expiration every five minutes. Once refresh tokens expire, it gets kicked off the store and fails the request validation.

This is what the refresh token response looks like:

This refresh token remains the same after each access token renewal. This is because the client configuration is set to ReUse. The spec says the identity provider can reuse the refresh token. Or, return a brand-new refresh token. It’s up to whatever makes one feel more secure.

Putting It All Together

Now that this identity provider is overflowing with tokens. It is time to secure an endpoint with a bearer token. The scaffold puts in a place a dummy controller that does weather forecasting. Find WeatherForecastController and open it, I’ll come back to this later.

To enable bearer JWT token authentication, add the following NuGet package:

In Startup, add this middleware:

This tells the client app where the authentication authority is so it can validate JWTs. The LifetimeValidator is a lambda expression that rejects expired tokens. I’m disabling HTTPS because I only plan to run this in local.

Client apps need a well-known configuration endpoint that comes from the identity provider. This is how they read the public key and validate tokens.

To include the well-known config endpoint that comes from Identity Server 4:

Feel free to poke around this endpoint. Go to http://localhost:5000/.well-known/openid-configuration in the browser.

With this, add an Authorize attribute to the Get method in WeatherForecastController. Strip out any dependencies from this class such as ILogger. Project scaffolding can add unnecessary dependencies in this class. Make sure the project is running in watch mode then fire off the following request:

Accessing this endpoint without a bearer token returns a 401 response. The request also gets rejected after the token expires.

Below is the typical use case for all these tokens. I’ll begin with resource owner tokens, call the protected endpoint, and refresh the token. Then, use client credential tokens to access the same endpoint.

Conclusion

Identity Server 4 is the best tool for generating bearer tokens. It comes with client credential, resource owner, and refresh tokens. Client configuration dictates which token flows are allowed with grant type and scopes. Startup configuration in .NET Core makes the integration easier. Bearer token authentication can go in any API endpoint by setting the right authority. The well-known config endpoint in the identity provider is how APIs validate tokens.