Using Identity Server To Authorize Our APIs

Introduction

In today’s article, we will look at using Identity Server 4 which is an OpenID Connect and OAuth 2.0 Framework for ASP.NET Core and .NET 5. We will see how to setup an Identity server and then use this server to authenticate our API calls.

Creating the Identity Server

We will start by creating a new ASP.NET MVC application in the Visual Studio 2019 community edition.

Using Identity Server to authorize our APIs

Using Identity Server to authorize our APIs

Using Identity Server to authorize our APIs

Using Identity Server to authorize our APIs

This will provide us a basic ASP.NET MVC application. Let us now add the required Nugget package (IdentityServer4).

Using Identity Server to authorize our APIs

Using Identity Server to authorize our APIs

Using Identity Server to authorize our APIs

Next, add a new class named “Config.cs” and add the below code,

using IdentityServer4.Models;
using System.Collections.Generic;
namespace IdentityServer {
    public static class Config {
        public static IEnumerable < ApiScope > ApiScopes => new List < ApiScope > {
            new ApiScope("testapis", "Test API Scope")
        };
        public static IEnumerable < Client > Clients => new List < Client > {
            new Client {
                ClientId = "testclient",
                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    ClientSecrets = {
                        new Secret("testsecret".Sha256())
                    },
                    AllowedScopes = {
                        "testapis"
                    }
            }
        };
    }
}

After that update, the “Startup.cs” file as below,

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;

namespace IdentityServer
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            var builder = services.AddIdentityServer()
                .AddDeveloperSigningCredential()
                .AddInMemoryApiScopes(Config.ApiScopes)
                .AddInMemoryClients(Config.Clients);

            services.AddRazorPages();

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

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

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

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
            });

        }
    }
}

Now, set the startup profile to the name of the project.

Using Identity Server to authorize our APIs

Next, run the application and the server will be activated. You can verify the settings by visiting the below URL,

Using Identity Server to authorize our APIs

At this point, the Identity server is working. Please note that we set a few things in the Config class. This includes the clients which will be able to request tokens from this server. In this example, I hard-coded only one client (testclient). However, in a real-life scenario, we would probably read this list of clients from a storage source like a database, etc. For each client, we set the scope, secret, and grant type. In this example, we use the client credentials as the grant type. The scope is used to provide access to certain resources. Hence, we can define different scopes in order to give different clients access to different resources. For simplicity, I have only defined one scope in this example.

We can now try to get a token from this server bypassing the required data to it. We can create an application for this but to keep things simple, I will make my request via Postman as below,

Using Identity Server to authorize our APIs

Here, you see that we get an access token. Kindly copy it to notepad as we will be using this in the next step.

Adding a controller to test the Identity Server

Next, we add a folder named “Controllers” to our project.

Using Identity Server to authorize our APIs

And add a new empty API controller to it.

Using Identity Server to authorize our APIs

Next, add the required Nugget package (Microsoft.AspNetCore.Authentication.JwtBearer).

Using Identity Server to authorize our APIs

Using Identity Server to authorize our APIs

Using Identity Server to authorize our APIs

After the package has been added, update the “ValuesController”.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace IdentityServer.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return new JsonResult("This request has been authorized");
        }
    }
}

Also, update the “Startup.cs” file as below,

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
namespace IdentityServer {
    public class Startup {
        public Startup(IConfiguration configuration) {
            Configuration = configuration;
        }
        public IConfiguration Configuration {
            get;
        }
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services) {
            var builder = services.AddIdentityServer().AddDeveloperSigningCredential().AddInMemoryApiScopes(Config.ApiScopes).AddInMemoryClients(Config.Clients);
            services.AddRazorPages();
            services.AddControllers();
            services.AddAuthentication("Bearer").AddJwtBearer("Bearer", options => {
                options.Authority = "https://localhost:5001";
                options.TokenValidationParameters = new TokenValidationParameters {
                    ValidateAudience = false
                };
            });
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
            if (env.IsDevelopment()) {
                app.UseDeveloperExceptionPage();
            } else {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseIdentityServer();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints => {
                endpoints.MapRazorPages();
            });
            app.UseEndpoints(endpoints => {
                endpoints.MapControllers();
            });
        }
    }
}

Here, we have added the code to ensure that any authentication using a bearer token is done by the Identity server running on port 5001 using HTTPS.

Next, to test the API we use Postman again and run the following,

Using Identity Server to authorize our APIs

Remember to select “Authorization” and “Bearer token” and then provide the token we copied in an earlier step. When we make the call we see the above data returned from the API. If we do not pass a valid token we will get the below,

Using Identity Server to authorize our APIs

Summary

In this article, we looked at how we can define an Identity server and then use this server to protect our APIs. I added both the server and API in the same project. However, these can be kept in separate projects if required. Similarly, I made calls to both the server and API via Postman. This can also be done via a client application.

Happy coding!