Learn ASP.NET Core 3.1 with mini project : MVC, Razor Pages, Web API, Entity Framework Core, and Blazor. Hands-on online training courses. Click here to know more.

Integrate IdentityServer with ASP.NET Core (Part 1 - Server Configuration)

IdentityServer is an implementation of OpenID Connect and OAuth2 specifications that ASP.NET Core developers can use to secure their applications and APIs. Beginners often find it bit tricky to setup and configure the applications to use this popular framework. To that end this article series attempts to provide a quick tour of setup and configuration required to put IdentityServer to use. I am not going to explain the basic OAuth2 and OpenID Connect terminology here. A lot of good resources are available on the internet explaining those basics. You can also go through the IdentityServer documentation to familiarize yourself with the framework.

In this multipart article you will develop three Visual Studio projects namely IdentityServerDemo.Server, IdentityServerDemo.WebApi, and IdentityServerDemo.Client. The following figure shows these projects upon completion.

The IdentityServerDemo.Server project contains the authorization server. It is the component that issues security tokens and deals with user authentication, access control, and user profile. The IdentityServerDemo.WebApi project contains a Web API - Employees Web API. This Web API is protected using JWT based authentication and also uses role based security. The Employees Web API can be called directly using HttpClient or a JavaScript client or it can be consumed by an MVC web application protected using IdentityServer. The IdentityServerDemo.Client is the MVC application that obtains the security token and forwards it to the Employees Web API. So, you will learn to protect Web APIs as well as MVC web applications using IdentityServer.

In this part of the series you will develop the IdentityServerDemo.Server project. You will revisit this project in the next parts also but this part adds many of the configuration settings to the IdentityServer.

Let's get started.

Begin by creating a new ASP.NET Core web application named IdentityServerDemo.Server using Visual Studio. You can either pick MVC template or Empty template while creating the project. Here, I am using Empty project template. 

Once the project is created, switch set the web server from IIS Express to Kestrel development server. You can do that using toolbar as shown below:

You can keep using IIS Express if you want but switching to Kestrel helps us tweak with the port number easily. Moreover, you get to see the console logs and messages easily.

Then open project's properties page, go to the Debug | Web Server settings section and note down the URL carefully. You need this URL during configuring IdentityServer related features.

Next, go to the GitHub repository of IdentityServer's QuickStart UI here and download the whole content (QuickStart, Views, and wwwroot folders) on your local machine. Add these folders to your IdentityServerDemo.Server project folder. Upon adding these folders, your Solution Explorer should show these folders and files. These files contain the default UI elements such as login page that you can easily reuse while developing your application.

Then add the IdentityServer4 NuGet package in your project.

Now we will add various pieces of IdentityServer configuration. These pieces can come from an in-memory store or they can come from some persistent store such as SQL Server database. For now we will use in-memory configuration.

Add a new class in the IdentityServerDemo.Server project called ServerConfiguration. The following code shows the skeleton of ServerConfiguration class.

public static class ServerConfiguration
{
  public static List<IdentityResource> IdentityResources {...}
  public static List<ApiScope> ApiScopes {...}
  public static List<ApiResource> ApiResources {...}
  public static List<Client> Clients {...}
  public static List<TestUser> TestUsers {...}
}

As you can see, ServerConfiguration contains five static properties namely IdentityResources, ApiResources, ApiScopes, Clients, and TestUsers. All these properties return a List containing the respective objects. For example, IdentityResources property returns a List of IdentityResource objects. The classes such as IdentityResource, ApiResource, ApiScope, Client, and TestUser are provided by IdentityServer.

Now that you know the overall structure of ServerConfiguration, let's add code to these properties one-by-one.

The IdentityResources property is shown below:

public static List<IdentityResource> IdentityResources
{
    get
    {
        List<IdentityResource> idResources = 
new List<IdentityResource>();
        idResources.Add(new IdentityResources.OpenId());
        idResources.Add(new IdentityResources.Profile());
        idResources.Add(new IdentityResources.Email());
        idResources.Add(new IdentityResources.Phone());
        idResources.Add(new IdentityResources.Address());
        idResources.Add(new IdentityResource("roles", 
"User roles", new List<string> { "role" }));
        return idResources;
    }
}

Identity resources are pieces of information about a user such as user's Open ID (subject id or sub), profile (given_name and family_name), email, phone, address, and roles. Here I am adding all these IdentityResource objects just for the sake of testing. You should add only those that you really need in the application. Notice how IdentityResource objects are obtained using methods such as OpenId(), Profile(), and Email(). We are going to use role based security for our Web API and MVC application. So, we also added roles resource.

The ApiScopes property is shown below:

public static List<ApiScope> ApiScopes
{
    get
    {
        List<ApiScope> apiScopes = 
new List<ApiScope>();
        apiScopes.Add(new ApiScope
("employeesWebApi", "Employees Web API"));
        return apiScopes;
    }
}

A scope indicates the limit of access that can be granted to an application. An application can request one or more scopes. If a user grants access to these scopes, the application receives a security token that can be used to access only these scopes. Here, we create a single ApiResource called employeesWebApi. The second parameter to the constructor indicates the display name of the scope on the user consent page (you will see that page later).

The ApiResources property is shown below:

public static List<ApiResource> ApiResources
{
    get
    {
        ApiResource apiResource1 = new 
ApiResource("employeesWebApiResource", 
"Employees Web API")
        {
            Scopes = { "employeesWebApi" },
            UserClaims = { "role",
                            "given_name",
                            "family_name",
                            "email",
                            "phone",
                            "address"
                            }
        };

        List<ApiResource> apiResources = new 
List<ApiResource>();
        apiResources.Add(apiResource1);

        return apiResources;
    }
}

An API resource indicates the Web API you want to protect. The ApiResources property creates a single ApiResource called employeesWebApiResource. Scopes and UserClaims properties of the ApiResource being created are also set to employeesWebApi scope created earlier and a set of identity pieces such as role, given_name, and family_name.

The Clients property is shown below:

public static List<Client> Clients
{
    get
    {
        Client client1 = new Client
        {
            ClientId = "client1",
            ClientName = "Client 1",
            ClientSecrets = new[] { 
new Secret("client1_secret_code".Sha512()) },
            AllowedGrantTypes = GrantTypes.
ResourceOwnerPasswordAndClientCredentials,
            AllowedScopes = {
                "openid",
                "roles",
                "employeesWebApi"
            }
        };

        List<Client> clients = new List<Client>();
        clients.Add(client1);

        return clients;
    }
}

We create new Client object and set properties such as ClientId, ClientName, ClientSecrets, AllowedGrantTypes, and AllowedScopes. This client is used to talk directly to the Employees Web API and so the GrantType is set to PasswordOwnerAndClientCredentials. In order to get a security token we will supply ClientId, ClientSecret, UserName, and Password from the client code. The AllowedScopes property indicates the scopes allowed for this client - openid, roles, and employeesWebApi. The client secret is SHA512 hash value of the secret code (client1_secret_code in this case).

We will add another client in the above code when we develop MVC web application in the later parts of this article series.

The TestUsers property is shown below:

public static List<TestUser> TestUsers
{
    get
    {
        TestUser usr1 = new TestUser()
        {
            SubjectId = "2f47f8f0-bea1-4f0e-ade1-88533a0eaf57",
            Username = "user1",
            Password = "password1",
            Claims = new List<Claim>
            {
                new Claim("given_name", "firstName1"),
                new Claim("family_name", "lastName1"),
                new Claim("address", "USA"),
                new Claim("email","user1@localhost"),
                new Claim("phone", "123"),
                new Claim("role", "Admin")
            }
        };

        TestUser usr2 = new TestUser()
        {
            SubjectId = "5747df40-1bff-49ee-aadf-905bacb39a3a",
            Username = "user2",
            Password = "password2",
            Claims = new List<Claim>
            {
                new Claim("given_name", "firstName2"),
                new Claim("family_name", "lastName2"),
                new Claim("address", "UK"),
                new Claim("email","user2@localhost"),
                new Claim("phone", "456"),
                new Claim("role", "Operator")
            }
        };

        List<TestUser> testUsers = new List<TestUser>();
        testUsers.Add(usr1);
        testUsers.Add(usr2);

        return testUsers;
    }
}

The TestUsers property creates two TestUser objects. A TestUser represents a user account that you can use during development for testing purposes. Each user has SubjectId, Username, Password, and a list of Claims. The SubjectId is a GUID. The Username and Password are user's login credentials. The Claims list is a set of claims belonging to that user. You can retrieve these claims in your Web API or MVC application if required. Here, we add given_name, family_name, address, email, phone, and role claims. Especially the role claim is important for our example because we will be implementing role based security in the Employees Web API and the MVC web application. The user1 belongs to Admin role and user2 belongs to Operator role.

This completes the ServerConfiguration class. Now we can use it in the Startup class. Let's go ahead and do just that.

Open the Startup.cs file and add the following code to its ConfigureServices() method.

public void ConfigureServices
(IServiceCollection services)
{
    services.AddControllersWithViews();

    services.AddIdentityServer()
            .AddInMemoryIdentityResources
(ServerConfiguration.IdentityResources)
            .AddInMemoryApiResources
(ServerConfiguration.ApiResources)
            .AddInMemoryApiScopes
(ServerConfiguration.ApiScopes)
            .AddInMemoryClients
(ServerConfiguration.Clients)
            .AddTestUsers
(ServerConfiguration.TestUsers)
            .AddDeveloperSigningCredential();
}

First, we register MVC related types with the DI container using AddControllersWithViews() method. Then we register IdentityServer related types using AddIdentityServer() method. Further, a series of methods starting from AddInMemotyApiResources() and ending with AddTestUsers() add the in-memory configuration for IdentityResources, ApiResources, ApiScopes, Clients, and TestUsers. Finally, AddDeveloperSigningCredential() method adds a development time digital signature to the configuration. In a more realistic case you will use AddSigningCredential() method to specify a custom signature.

Now go to the Configure() method and add the following code.

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

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

    app.UseIdentityServer();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}
/{action=Index}/{id?}");
    });

}

Notice the code in bold letters. It wires the IdentityServer middleware into the request pipeline.

Build and run the IdentityServerDemo.Server project. If all goes well, you will see this in the browser:

In the next part of this series we will develop the Employees Web API project.

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant, trainer, author, yoga mentor, and meditation teacher. He has been programming, meditating, and teaching for 24+ years. He conducts instructor-led online training courses in ASP.NET family of technologies for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get connected : Facebook  Twitter  LinkedIn  YouTube

Posted On : 03 August 2020


Tags : ASP.NET ASP.NET Core SQL Server MVC C# Visual Studio Configuration


Subscribe to our newsletter

Get monthly email updates about new articles, tutorials, code samples, and how-tos getting added to our knowledge base.

  

Receive Weekly Updates