We have set up our identity server but we lack UI for the users to enter their credentials. We’ve shown how we can retrieve our token but for better user experience, we have to add UI to our Authorization Server. So, adding IdentityServer4 UI is our goal for this article. Additionally, we are going to learn how we can protect our API and the way to access protected resources. This will be a basic protection setup, but we are going to enhance it during this series.

To download the source code for the starting projects, you can visit the IdentityServer4 UI repository (Start folder). For the ending project’s source code, you can visit the IdentityServer4 UI repository (End folder).

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

Let’s start.

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

Installing IdentityServer4 UI and Overview

If we start our authorization server now, we are going to see a not-found page in a browser for sure. Even though the server works, this is not user-friendly at all. So let’s do something about that. There is already created UI for the IdentityServer which is easy to install and modify, and we are going to use it for our project.

So, the first thing we are going to do is to open a PowerShell window and navigate to the folder where we have our OAuth project (…\CompanyEmployees.OAuth\CompanyEmployees.OAuth). Once the window is up, let’s execute a command to download the entire UI project:

iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/main/getmain.ps1'))

After a couple of seconds, we can see a confirmation about installed Quickstart, Views, and wwwroot folders:

IdentityServer4 UI installed confirmation

File Inspection And Configuration

If we inspect the SolutionExplorer window of our project, we are going to see three additional folders:

IdentityServer4 UI Inspection

We can see the wwwroot folder with the static files required for the UI. There is the Quickstart folder with different controllers, models, and additional classes. Finally, there is the Views folder with different views (.cshtml files) inside. Everything we downloaded is easy to maintain and modify so, this speeds up the UI creation process a lot. For our examples the default created files will be quite enough.

Now, we have to configure this UI.

So, the first thing we are going to do is to modify the Configure method in the Startup class:

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

    app.UseStaticFiles();
    app.UseRouting();

    app.UseIdentityServer();

    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

With the UseStaticFiles method, we enable serving static files from the wwwroot folder. Additionally, we are adding routing and authorization to the pipeline and configuring endpoints to use a default /Home/Index endpoint. If we open the Quickstart/Home folder, we are going to find a HomeController with the Index action inside. So, this is our entry point.

We have to modify the ConfigureServices method as well:

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;

        services.AddControllersWithViews();
}

Excellent. Now, we can test our UI.

Testing IdentityServer4 UI

Let’s start the application. It should automatically navigate to localhost:5005 and show the Welcome page:

Welcome page IdentityServer4

If we click the discovery document link, we are going to see different endpoints we’ve been talking about in a previous article.

Once we click the second link, we are going to be directed to the login page and after we enter Mick’s credentials, we are going to see all the claims:

Authentication Cookie Page IdentityServer UI

Now, if we click the second “here” link, for the stored grants, we are going to be directed to the login page but as soon as we enter credentials, we are going to see the message informing us about not given access to any applications:

Login IdentityServer4 UI

So, we see everything is working as expected. We can logout as well by clicking the username (Mick) and then the Logout link. In the rest of the series, we are going to use this UI quite frequently.

Now, let’s see how we can protect our API using the authorization server.

Configuring API Resources

The first thing we have to do is to add our API scopes in the Authorization Server:

public static IEnumerable<ApiScope> GetApiScopes() =>
    new List<ApiScope> { new ApiScope("companyApi", "CompanyEmployee API") };

Then, as we did with the IdentityResources, in a previous article to support different identity-related data, we are going to support our API resource as well. So, let’s add additional code in the InMemoryConfig class in the OAuth project:

public static IEnumerable<ApiResource> GetApiResources() =>
    new List<ApiResource> 
    { 
        new ApiResource("companyApi", "CompanyEmployee API") 
        { 
            Scopes = { "companyApi" } 
        } 
    };

As you can see, we can add multiple API resources, but for now, we add only one with the name and displayname parameters. Also, we have to specify the scopes related to this resource.

Additionally, we have to modify the 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, "companyApi" }
         }
     };

We just add additional scope to support our API scope.

Finally, we have to include this configuration in the ConfigureServices method:

 services.AddIdentityServer()
     .AddInMemoryApiScopes(InMemoryConfig.GetApiScopes())
     .AddInMemoryApiResources(InMemoryConfig.GetApiResources())
     .AddInMemoryIdentityResources(InMemoryConfig.GetIdentityResources())
     .AddTestUsers(InMemoryConfig.GetUsers())
     .AddInMemoryClients(InMemoryConfig.GetClients())
     .AddDeveloperSigningCredential(); //not something we want to use in a production environment;

That is all regarding the IdentityServer configuration and we can continue with the API security logic.

Securing Web API

Before we configure our middleware to support IdentityServer, we have to install a Nuget package to help us in the process:

JwtBearer package

After the installation, we can modify the ConfigureServices method in the Startup class:

services.AddAuthentication("Bearer")
   .AddJwtBearer("Bearer", opt =>
   {
       opt.RequireHttpsMetadata = false;
       opt.Authority = "https://localhost:5005";
       opt.Audience = "companyApi";
   });

services.AddControllers();

We use the AddAuthentication method to add authentication services to the IOC in our project. Moreover, we use the AddJwtBearer method to configure support for our Authorization Server. In that method, we specify several options:

  • RequireHttpsMetadata – We set this to false because we are in a development environment and we don’t require HTTPS
  • Authority – Address to use when sending OpenID Connect calls
  • Audience – Audience value for received OpenID Connect tokens. This value has to be the same as the one provided in the authorization server configuration for the API resource

We have to do one more thing in the Configure class:

app.UseAuthentication();
app.UseAuthorization();

Now, all we have to do is to protect our action in the CompaneesController by placing the [Authorize] attribute on top of the action or controller itself. For that, we have to include Microsoft.AspNetCore.Authorization endpoint in the controller.

Let’s start both applications and send a request with Postman:

Unauthorized access to web api

As expected, we get the 401 – Unauthorized response.

Now, to provide access to the authorized resource, we are going to send another request, but to the authorization server first:

Token received from auth server

Great, we have a token. Let’s copy our token and add it to the previous request as the Authorization parameter in the Headers tab:

Retreived data from API

And, there we go. We have the required data. So, we are authorized to communicate with our API.

Inspecting Console Logs

Now, let’s take a look at the console window of the IdentityServer project:

API call to IdentityServer

What we can see here is that our API sent a request to the /.well-known/openid-configuration/jwks endpoint to retrieve a public key from the identity server to validate our token.

We can validate that this communication is taking place by modifying our token in Postman. If we do that, we are going to get 401 for sure as a result, because the token is tempered with and is valid no more:

Tempered token - IdentityServer4 UI

Excellent. We can see the communication between our API and the Identity Server is working flawlessly.

Inspecting Claims

Finally, we can inspect the claims from the token. To do that let’s add a single code line in the GetCompanies action:

var claims = User.Claims;

After we acquire a valid token again and send a request to our API, we can see all the claims:

Inspecting User Claims

So, we can see all the data from the access token provided as a Bearer token in our request. And we can confirm that this token is not tampered with, because at this point, the token is already validated and the API has all the required information from it.

Of course, we’ve used the Client_Credentials flow in this example, but if you use the ResourceOwnerPassword flow, we are going to get additional claims for sure:

Inspecting User Claims ResourceOwnerPassword flow

If we decode this token, we are going to get the same result:

Decoded token

Excellent. Everything works as expected.

Conclusion

So, we have learned how to add the UI part to our authorization server and also how to protect our Web API by using two different flows. Of course, we are going to learn more about API protection with additional flows and we are going to use this UI for that purpose as well.

In the next article, we are going to learn how to use the Hybrid flow to protect our web application.

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