In this article, we are going to learn about IdentityServer4 Integration with the ASP.NET Core project. We are going to start with some explanations related to JWT, OAuth, OpenIdConnect, and Endpoints, to get familiar with the basics. After the theory part, we are going to jump into the code and explain IdentityServer4 integration.

To download the source code for this project, you can visit the IdentityServer4 Integration repository.

To navigate through the entire series, visit the IdentityServer4 series page.

About a Token and How We Can Use It

Before we start learning about OAuth and OpenID Connect, we have to understand what the token is. If we want to access a protected resource, the first thing we have to do is to retrieve a token. When we talk about token-based security, most of the time we refer to the JSON web token or JWT. We have great articles about JWT authentication with ASP.NET Core so, we won’t explain JWT here in-depth. It is a JSON object we use for secure data transmission. Again, feel free to visit the linked article, to learn more about JWT.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Now, let’s see how we exchange our Credentials for a Token:

  • A user provides credentials to the authorization server and the server responds with a token
  • After that, a user can use that token to talk to the API and retrieve the required data. Of course, behind the scenes, the API will validate that token and decide whether the user has access or not to the requested endpoint
  • Finally, a user can use a token with the third-party application that communicates with the API and retrieves data from it. Of course, a third-party application has to provide a token to the API via headers. After the token validation, the API provides data to the client application and that client application returns data to a user.

As we can see, we are not using our credentials with the third-party application, instead, we use a token, which is a more secure way.

About OAuth2 and OpenID Connect

OAuth2 and OpenId Connect are protocols that allow us to build more secure applications. OAuth2 is the industry-standard protocol for authorization. It delegates user authentication to the service that hosts the user’s account and authorizes third-party applications to access that account. It provides different flows for our applications, whether they are web applications or desktop or mobile applications. OAuth stands for Open standard for Authorization.

With authorization, we prove that we have access to a certain endpoint. But if we want to add authentication in the process, we have to refer to the OpenId Connect. So, OpenID Connect complements OAuth2 with the authentication part. It is a simple identity layer on top of the OAuth2 protocol that allows clients to verify their identity after they perform authentication on the authorization server. We can extract more information about the end-user by using OpenID Connect.

OAuth2 and OpenID Endpoints and Flows

It is quite important to have in mind that there is great documentation regarding OAuth 2.0 – RFC 6749 that helps us a lot to understand OAuth related topics. One of those topics is related to the OAuth endpoints. So, let’s inspect them:

  • /authorize – a client uses this endpoint (Authorization endpoint) to obtain authorization from the resource owner. We can use different flows to obtain authorization and gain access to the API
  • /token – a client uses this endpoint to exchange an authorization grant for an access token. This endpoint is used for the token refresh actions as well
  • /revocation – this endpoint enables the token revocation action.

OpenID Connect allows us to do some additional things with different endpoints:

  • /userinfo – retrieves profile information about the end-user
  • /checksession – checks the session of the current user
  • /endsession – ends the session for the current user

There are different flows we can use to complete authorization actions: Implicit, Authorization Code, Resource Owner Password Credentials, Client Credentials, Hybrid (mix of authorization code and implicit flow).

The flow determines how the token is returned to the client and each flow has its specifics.

You can read more about these different flows at the mentioned documentation as well.

IdentityServer4 Integration

After we’ve learned the basics, we can start with the coding part.

Let’s start by creating an empty web application:

OAuth Project Creation - IdentityServer4 Integration

After that, let’s modify the launchsettings.json file:

{
  "profiles": {
    "CompanyEmployees.OAuth": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5005;http://localhost:5006",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Now, let’s install a required IdentityServer4 package (Do not use the preview version of this library, use the last stable version):

Nuget Package IdentityServer4 Integration

After the installation, we are going to add IdentityServer to our application by modifying the ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentityServer();
}

Additionally, we have to add IdentityServer to the request pipeline by modifying the Configure method:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseIdentityServer();
}

As you can see, we have removed an additional code from this method because we don’t need it.

So, if we try to start the application now, we are going to get an error. That’s because IdentityServer requires an additional configuration. So, let’s add the required code.

IdentityServer4 In-Memory Configuration

The first thing, we are going to do is create a new Configuration folder with the InMemoryConfig class inside. This class will consist of different configurations related to Users, Clients, IdentityResources, etc. So, let’s add them one by one.

First, we are going to add IdentityResources:

public static class InMemoryConfig
{
    public static IEnumerable<IdentityResource> GetIdentityResources() =>
      new List<IdentityResource>
      {
          new IdentityResources.OpenId(),
          new IdentityResources.Profile()
      };
}

For IdentityResource, we have to use IdentityServer4.Models namespace. Identity resources map to scopes that give access to identity-related information. With the OpenId method, we support a subject id or sub value to be included. We include the Profile method as well to support profile information like given_name or family_name.

After that, let’s add the users into the configuration:

public static List<TestUser> GetUsers() =>
  new List<TestUser>
  {
      new TestUser
      {
          SubjectId = "a9ea0f25-b964-409f-bcce-c923266249b4",
          Username = "Mick",
          Password = "MickPassword",
          Claims = new List<Claim>
          {
              new Claim("given_name", "Mick"),
              new Claim("family_name", "Mining")
          }
      },
      new TestUser
      {
          SubjectId = "c95ddb8c-79ec-488a-a485-fe57a1462340",
          Username = "Jane",
          Password = "JanePassword",
          Claims = new List<Claim>
          {
              new Claim("given_name", "Jane"),
              new Claim("family_name", "Downing")
          }
      }
  };

For the TestUser and Claim classes, we have to include additional namespaces:

using IdentityServer4.Test;
using System.Security.Claims;

As we can see, these users have SubjectId supported by the OpenId IdentityResource and the given_name and family_name claims supported by the Profile IdentityResource.

Finally, let’s add a client:

public static IEnumerable<Client> GetClients() =>
    new List<Client>
    {
       new Client
       {
            ClientId = "company-employee",
            ClientSecrets = new [] { new Secret("codemazesecret".Sha512()) },
            AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
            AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId }
        }
    };

So, we provide the ClientId and the ClientSecret for this client. The secret is hashed with the Sha512 algorithm. The AllowedGrantTypes provides the information about the flow we are going to use to get the token. For this initial example, we use ResourceOwnerPasswordAndClientCredentials flow which allows us to trade user credentials for the token. Additionally, we can use this flow to exchange only the ClientId and the Secret for the token. Finally, we just provide allowed scopes for the client.

Excellent. Now, we can register this in the Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentityServer()
        .AddInMemoryIdentityResources(InMemoryConfig.GetIdentityResources())
        .AddTestUsers(InMemoryConfig.GetUsers())
        .AddInMemoryClients(InMemoryConfig.GetClients())
        .AddDeveloperSigningCredential(); //not something we want to use in a production environment;
}

Here, we register in-memory identity resources, test users and clients. Additionally, we use the AddDeveloperSigningCredential method to set temporary signing credentials. It’s quite enough for the development environment. For the production environment, you should use the AddSigningCredentials method and provide a valid certificate.

Testing Our Authorization Server

Once we start our application, we are going to get information that our server is up and running and using in-memory grant store:

Authorization server started

So, let’s try now to retrieve a token from our authorization server with a Postman request:

ResourceOwnerPassword flow - IdentityServer4 Integration

As you can see, we are using /connect/token endpoint to retrieve the token from the server. For parameters, we provide client-id, client_secret, password as a grant_type because we want to exchange user credentials for the token, and username and password. Once we press the Send button, we are going to receive our token:

Token for user credentials

Next to the access token, we have an expiration period, the token type, and the allowed scope for the client.

We can inspect the console logs as well:

Console logs for the token - IdentityServer4 Integration

We can see an end-point we use to retrieve the token, the message stating the validation has passed, and the information about the client. So, everything works as expected. But, if we try to modify, for example, client_id parameter:

Invalid request

We can see the request fails. Additionally, we can confirm this from the logs as well:

Invalid request console log

Great job.

As of IdentityServer library version 4, we can’t just send the request with the client_credentials grant type, the server returns an error about invalid scopes. But we will create our scopes in the next article and see how our API use client_credentials grant type to communicate with the authorization server.

Conclusion

Our authorization server works and we can retrieve a token from it using different flows.

So we can sum up. We have learned:

  • About Token, Endpoint, and Flows
  • How to integrate IdentityServer4 in the ASP.NET Core application
  • The way to setup in-memory configuration
  • How to retrieve tokens with the ResourceOwnerPassword flow

Our server doesn’t have a valid UI, but in the next article, we are going to add that feature. Additionally, we are going to learn how to protect our Web API project.

So, see you there.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!