Retrieving, using and validating token from an IdentityServer

campelo

Flavio Campelo

Posted on September 6, 2022

Retrieving, using and validating token from an IdentityServer
📮 Contact 🇧🇷 🇺🇸 🇫🇷

Twitter
LinkedIn


This is an example of how to a client application retrieves a token from a Identity Server and use it in an WebApi to consumes an endpoint.

How this project was created

This example was built based on Duende documentation

Identity Server

Identity provider for our sample.

Install Duende templates

dotnet new --install Duende.IdentityServer.Templates
Enter fullscreen mode Exit fullscreen mode

Create an empty solution

dotnet new sln -n IdentityServerDemo
Enter fullscreen mode Exit fullscreen mode

Create Identity server project

dotnet new isempty -n IdentityServer
Enter fullscreen mode Exit fullscreen mode

Add IdendityServer projet to the blank solution

dotnet sln add .\IdentityServer\IdentityServer.csproj
Enter fullscreen mode Exit fullscreen mode

Add scope to config.cs

public static IEnumerable<ApiScope> ApiScopes =>
    new ApiScope[]
        { new ApiScope(name: "campelo-api", displayName: "CampeloAPI") };
Enter fullscreen mode Exit fullscreen mode

Add client to config.cs

public static IEnumerable<Client> Clients =>
    new Client[]
        { new Client
        {
            ClientId = "client",
            // no interactive user, use the clientid/secret for authentication
            AllowedGrantTypes = GrantTypes.ClientCredentials,
            // secret for authentication
            ClientSecrets =
            {
                new Secret("secret".Sha256())
            },
            // scopes that client has access to
            AllowedScopes = { "campelo-api" }
        } };
Enter fullscreen mode Exit fullscreen mode

Openid-configuration

Now you can access openid-configuration from the identity server. Run the solution and go to https://localhost:5001/.well-known/openid-configuration. So you can retrieve something like this.

{
   "issuer":"https://localhost:5001",
   "jwks_uri":"https://localhost:5001/.well-known/openid-configuration/jwks",
   "authorization_endpoint":"https://localhost:5001/connect/authorize",
   "token_endpoint":"https://localhost:5001/connect/token",
   "userinfo_endpoint":"https://localhost:5001/connect/userinfo",
   "end_session_endpoint":"https://localhost:5001/connect/endsession",
   "check_session_iframe":"https://localhost:5001/connect/checksession",
   "revocation_endpoint":"https://localhost:5001/connect/revocation",
   "introspection_endpoint":"https://localhost:5001/connect/introspect",
   "device_authorization_endpoint":"https://localhost:5001/connect/deviceauthorization",
   "backchannel_authentication_endpoint":"https://localhost:5001/connect/ciba",
   "frontchannel_logout_supported":true,
   "frontchannel_logout_session_supported":true,
   "backchannel_logout_supported":true,
   "backchannel_logout_session_supported":true,
   "scopes_supported":[
      "openid",
      "campelo-api",
      "offline_access"
   ],
   "claims_supported":[
      "sub"
   ],
   "grant_types_supported":[
      "authorization_code",
      "client_credentials",
      "refresh_token",
      "implicit",
      "urn:ietf:params:oauth:grant-type:device_code",
      "urn:openid:params:grant-type:ciba"
   ],
   "response_types_supported":[
      "code",
      "token",
      "id_token",
      "id_token token",
      "code id_token",
      "code token",
      "code id_token token"
   ],
   "response_modes_supported":[
      "form_post",
      "query",
      "fragment"
   ],
   "token_endpoint_auth_methods_supported":[
      "client_secret_basic",
      "client_secret_post"
   ],
   "id_token_signing_alg_values_supported":[
      "RS256"
   ],
   "subject_types_supported":[
      "public"
   ],
   "code_challenge_methods_supported":[
      "plain",
      "S256"
   ],
   "request_parameter_supported":true,
   "request_object_signing_alg_values_supported":[
      "RS256",
      "RS384",
      "RS512",
      "PS256",
      "PS384",
      "PS512",
      "ES256",
      "ES384",
      "ES512",
      "HS256",
      "HS384",
      "HS512"
   ],
   "authorization_response_iss_parameter_supported":true,
   "backchannel_token_delivery_modes_supported":[
      "poll"
   ],
   "backchannel_user_code_parameter_supported":true
}
Enter fullscreen mode Exit fullscreen mode

API project

Web API project.

Create a new web API project

dotnet new webapi -n WebApi
Enter fullscreen mode Exit fullscreen mode

Adding webapi to the solution

dotnet sln add .\WebApi\WebApi.csproj
Enter fullscreen mode Exit fullscreen mode

Adding JWT Bearer authentication

dotnet add .\WebApi\WebApi.csproj package Microsoft.AspNetCore.Authentication.JwtBearer
Enter fullscreen mode Exit fullscreen mode

Set JWT Bearer as the default authentication scheme

Update the Program.cs or Startup.cs file.

builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "https://localhost:5001";

        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false
        };
    });

/// UseAuthentication right before UseAuthorization
app.UseAuthentication();
app.UseAuthorization();
Enter fullscreen mode Exit fullscreen mode

Add a new controller for testing

[Route("identity")]
[Authorize]
public class IdentityController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
}
Enter fullscreen mode Exit fullscreen mode

Configure the WebAPI to listen on Port 6001

Edit the launchSettings.json file in Properties folder.

"applicationUrl": "https://localhost:6001"
Enter fullscreen mode Exit fullscreen mode

Run the WebAPI project

Now you have a 401 (unauthorized) error response when trying to access https://localhost:6001/identity

Client project

Create a console client project

dotnet new console -n Client
Enter fullscreen mode Exit fullscreen mode

Add the client project to the solution

dotnet sln add .\Client\Client.csproj
Enter fullscreen mode Exit fullscreen mode

Add IdentityModel to the client project

dotnet add .\Client\Client.csproj package IdentityModel
Enter fullscreen mode Exit fullscreen mode

Discover endpoints from metadata

var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");
if (disco.IsError)
{
    Console.WriteLine(disco.Error);
    return;
}
Enter fullscreen mode Exit fullscreen mode

Request token

var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
    Address = disco.TokenEndpoint,

    ClientId = "client",
    ClientSecret = "secret",
    Scope = "campelo-api"
});

if (tokenResponse.IsError)
{
    Console.WriteLine(tokenResponse.Error);
    return;
}

Console.WriteLine(tokenResponse.AccessToken);
Enter fullscreen mode Exit fullscreen mode

Call WebAPI

var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);

var response = await apiClient.GetAsync("https://localhost:6001/identity");
if (!response.IsSuccessStatusCode)
{
    Console.WriteLine(response.StatusCode);
}
else
{
    var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
    Console.WriteLine(JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true }));
}
Enter fullscreen mode Exit fullscreen mode

Run client

Be sure that the IdentityServer and WebAPI are running and run the Client project to see something like this
Image 1

Source code

Source code

Typos or suggestions?

If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. If you feel comfortable with github, instead of posting a comment, please go directly to https://github.com/campelo/documentation and open a new pull request with your changes.

💖 💪 🙅 🚩
campelo
Flavio Campelo

Posted on September 6, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related