IdentityServer4 series | client credential mode

1, Foreword

From the previous article about Quickly build simple projects In, we simply built our identity server authorization server by manual or official template, made corresponding configuration and UI configuration, and realized the way of obtaining Token.

Among them, we also noticed three points: which users can access our API protection resources (APIs) through which clients.

Therefore, in this article, we will explain the client credential mode among various authorization modes, mainly focusing on the introduction of the resources of the identity server protection API and the client authentication authorization to access API resources.

2, First acquaintance

Client Credentials client credentials mode: the client requests the authorization server for authentication, and sends an access token after authentication. The client directly accesses some protected resources of the Resource server in its own name.

Users use this token to access the resource server. When the token fails, they use the refresh token to exchange for a new token (the effective time of the refresh token is longer than the access token, and the function of the refresh token is not described in detail)

The token given in this way is for third-party applications, not for users, that is, multiple users may share the same token.

2.1 scope of application

This mode is generally only used for authentication between servers

It is applicable to command line applications without a front end, that is, requesting tokens on the command line

The authentication server does not provide important resources such as user data, but only limited read-only resources or some open APIs. For example, a third-party static file service is used, such as Google Storage or Amazon S3. In this way, your application needs to read or modify these resources through external API calls and as the application itself rather than a single user. Such a scenario is very suitable for using client certificate authorization.

2.2 Client Credentials process:

 +---------+                                  +---------------+
 |         |                                  |               |
 |         |>--(A)- Client Authentication --->| Authorization |
 | Client  |                                  |     Server    |
 |         |<--(B)---- Access Token ---------<|               |
 |         |                                  |               |
 +---------+                                  +---------------+

Client credential licensing process description

(A) The client authenticates with the authorization server and requests an access token from the token endpoint.
(B) The authorization server authenticates the client and issues an access token if it is valid.

2.2.1 process details

Access token request
parameter Is it necessary meaning
grant_type essential Authorization type, with a fixed value of "client_credentials".
scope Optional Indicates the scope of authorization.

Example:
There are two methods of client authentication
1,Authorization: Bearer base64(resourcesServer:123)
2,client_id (client ID), client_secret (client secret key).

POST /token HTTP/1.1
Host: authorization-server.com
 
grant_type=client_credentials
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx

2.2.2 access token response

Refresh token should not be included.
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"Bearer",
"expires_in":3600,
"scope":"server"
}

3, Practice

In the example practice, we will create an authorized access service, define an API and the client to access it. The client requests an access token on identity server and uses it to access the API.

3.1 build Authorization Server service

Build certification and authorization services

3.1.1 installing Nuget package

IdentityServer4 package

Configuration content

Create configuration content file config cs

public static class Config
{
    public static IEnumerable<IdentityResource> IdentityResources =>
        new IdentityResource[]
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
    };


    public static IEnumerable<ApiScope> ApiScopes =>
        new ApiScope[]
    {
        new ApiScope("client_scope1")
    };

    public static IEnumerable<ApiResource> ApiResources =>
        new ApiResource[]
    {
        new ApiResource("api1","api1")
        {
            Scopes={"client_scope1" }
        }
    };

    public static IEnumerable<Client> Clients =>
        new Client[]
    {
        // m2m client credentials flow client
        new Client
        {
            ClientId = "credentials_client",
            ClientName = "Client Credentials Client",

            AllowedGrantTypes = GrantTypes.ClientCredentials,
            ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) 			},

            AllowedScopes = { "client_scope1" }
        },
    };
}

3.1.3 registration services

In startup Add the following code to the ConfigureServices method in CS:

        public void ConfigureServices(IServiceCollection services)
        {
            var builder = services.AddIdentityServer();
              // .AddTestUsers(TestUsers.Users);

            // in-memory, code config
            builder.AddInMemoryIdentityResources(Config.IdentityResources);
            builder.AddInMemoryApiScopes(Config.ApiScopes);
            builder.AddInMemoryApiResources(Config.ApiResources);
            builder.AddInMemoryClients(Config.Clients);

            // not recommended for production - you need to store your key material somewhere secure
            builder.AddDeveloperSigningCredential();
        }

3.1.4 pipeline configuration

In startup Add the following code to the Configure method in CS:

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

            app.UseRouting();
            app.UseIdentityServer();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });
        }

The above content is a way to quickly build a simple identity server project service. See the specific instructions Last Content of the.

3.2 build API resources

Protect API resources

3.2.1 quickly build an API project

3.2.2 installing Nuget package

IdentityServer4.AccessTokenValidation package

3.2.3 registration services

In startup Add the following code to the ConfigureServices method in CS:

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

        services.AddAuthentication("Bearer")
          .AddIdentityServerAuthentication(options =>
          {
              options.Authority = "http://localhost:5001";
              options.RequireHttpsMetadata = false;
              options.ApiName = "api1";
          });
    }

AddAuthentication configures Bearer to the default mode and adds the identity authentication service to DI.

Addidentityserver authentication adds the access token of the IdentityServer to the DI for use by the identity authentication service.

3.2.4 pipeline configuration

In startup Add the following code to the Configure method in CS:

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }    
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });
        }

UseAuthentication adds authentication middleware to the pipeline;

UseAuthorization adds the startup authorization middleware to the pipeline to perform the authentication authorization function every time the host is called.

2.5 add API resource interface

[Route("api/[Controller]")]
[ApiController]
public class IdentityController:ControllerBase
{
    [HttpGet("getUserClaims")]
    [Authorize]
    public IActionResult GetUserClaims()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
}

Add [Authorize] in the IdentityController controller. When requesting resources, you need to authenticate and Authorize before accessing.

3.3 build Client

Realize the access to API resources and obtain resources

3.3.1 build a window program

3.3.2 installing Nuget package

IdentityModel package

3.3.3 get token

The client authenticates with the authorization server and requests an access token from the token endpoint. The authorization server authenticates the client and issues an access token if it is valid.

IdentityModel includes a client library for discovering various endpoints of IdentityServer.

We can use the Token endpoint obtained from the identity server metadata to request a Token:

        private void getToken_Click(object sender, EventArgs e)
        {
            var client = new HttpClient();
            var disco = client.GetDiscoveryDocumentAsync(this.txtIdentityServer.Text).Result;
            if (disco.IsError)
            {
                this.tokenList.Text = disco.Error;
                return;
            }
            //Request token
            tokenResponse = client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
            {
                Address = disco.TokenEndpoint,
                ClientId =this.txtClientId.Text,
                ClientSecret = this.txtClientSecret.Text,
                Scope = this.txtApiScopes.Text
            }).Result;

            if (tokenResponse.IsError)
            {
                this.tokenList.Text = disco.Error;
                return;
            }
            this.tokenList.Text = JsonConvert.SerializeObject(tokenResponse.Json);
            this.txtToken.Text = tokenResponse.AccessToken;
        }

3.3.4 calling API

To send a Token to the API, you usually use the HTTP Authorization header. This is done using the SetBearerToken extension method.

    private void getApi_Click(object sender, EventArgs e)
    {
        //Call authentication api
        if (string.IsNullOrEmpty(txtToken.Text))
        {
            MessageBox.Show("token Value cannot be empty");
            return;
        }
        var apiClient = new HttpClient();
        //apiClient.SetBearerToken(tokenResponse.AccessToken);
        apiClient.SetBearerToken(this.txtToken.Text);

        var response = apiClient.GetAsync(this.txtApi.Text).Result;
        if (!response.IsSuccessStatusCode)
        {
            this.resourceList.Text = response.StatusCode.ToString();
        }
        else
        {
            this.resourceList.Text = response.Content.ReadAsStringAsync().Result;
        }

    }

If you don't understand the code shown above, you can see the source code of this project. The project address is:

3.4 effect

3.4.1 project test

3.4.2 postman test

4, Question

Note that if your code is OK but still reports errors, such as "invalid scope", "Audience validation failed" and so on.

In 3.1 X to 4 In the change of X, the scope of ApiResource is officially independent as an ApiScope object, which distinguishes the relationship between ApiResource and scope. Scope is an attribute of ApiResource and can contain multiple scopes.

Therefore, in configuring ApiResource, ApiScope and Clients, we need to pay attention to some places:

In 3 In X version

 public static IEnumerable<ApiResource> GetApiResources()
 {
     return new[] { new ApiResource("api1", "api1") };
 }

Change to 4 X version is

public static IEnumerable<ApiResource> ApiResources =>
    new ApiResource[]
{
    new ApiResource("api1","api1")
    {
        Scopes={"client_scope1" }
    }
};

public static IEnumerable<ApiScope> ApiScopes =>
    new ApiScope[]
{
    new ApiScope("client_scope1")
};

So,

It's 3.5 times better than before Version x has an additional method to add APIs scopes:

builder.AddInMemoryApiScopes(Config.ApiScopes);

Since there are API resources to be protected next, you need to add a line:

builder.AddInMemoryApiResources(Config.ApiResources);
  1. If in 4 In version x, if the APIs scopes method is not added, there will always be "invalid scope" and other errors when obtaining the token token
  2. When authorizing access to protected resources, if Scopes is not added to ApiResource, an Audience validation failed error will be reported all the time and a 401 error will be obtained, so in 4.4 The writing method in X version is different from that in 3 X version

Therefore, it should be noted that 4 X version of ApiScope and ApiResource are configured separately, and then Scopes must be added to ApiResource.

5, Summary

  1. This chapter mainly authorizes in the client credential mode. We create an authentication authorization access service, define an API and the client to access it. The client requests an access token on identity server and uses it to control the access API.
  2. The possible problems in the article are solved by searching, as well as the differences between the previous and previous versions, and the problems are summarized and explained.
  3. In the follow-up, we will further explain other authorization modes, database persistence, and how to apply them in API resource server and configure them in client.
  4. If there is something wrong or incomprehensible, I hope you can make more corrections, ask questions, discuss together, keep learning and make common progress.
  5. Project address

6, Attach

Client Authentication authentication

Client credentials data

Tags: IdentityServer4

Posted by bseven on Tue, 10 May 2022 04:34:10 +0300