Sunday, May 28, 2023

Duende IdentityServer ClientCredential flow

 Duende (Identity server)  is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core. Duende Identity Server enables the following security features:


  • Authentication as a Service (AaaS)

  • Single sign-on/off (SSO) over multiple application types

  • Access control for APIs

  • Federation Gateway




In this blog, we are going to set up IdentityServer (Duende ): protecting APIs for server-to-server communication.


We are going to create a solution containing three projects:

  1. An Identity Server

  2. An API that requires authentication

  3. A client that accesses that API








 Source code : https://github.com/shedev/duende.git



Folder structure

Inside src folder, there are 3 folder each containing project as shown


 

Creation of solution:

 Create of root folder  as Duendo and subfolder as src and then create  a empty solution as duendosln

mkdir Duendo
cd quickstart
mkdir src
dotnet new sln -n duendosln

Set Up Identity server:

  •  Install  IdentityServer templates for the dotnet CLI

   dotnet new install Duende.IdentityServer.Templates

      


  • Create a web project from the installed above template by following command inside the src folder 

cd src

dotnet new isempty -n IdentityServer

This will create the following files within a new src/IdentityServer directory:

IdentityServer.csproj - project file with the IdentityServer nuget package added

Properties/launchSettings.json file - launch profile

appsettings.json - run time settings

Program.cs - main application entry point

HostingExtensions.cs - configuration for ASP.NET pipeline and services Notably, the IdentityServer services are configured here and the IdentityServer middleware is added to the pipeline here.

Config.cs - definitions for resources and clients used by IdentityServer


  • Add the IdentityServer project to the solution


cd ..
dotnet sln add ./src/IdentityServer/IdentityServer.csproj

  • Defining an API Scope (src/IdentityServer/Config.cs)
public static IEnumerable<ApiScope> ApiScopes =>
    new List<ApiScope>
    {
        new ApiScope(name: "api1", displayName: "MyAPI") 
    };
  • Defining an Client (src/IdentityServer/Config.cs)
public static IEnumerable<Client> Clients =>
    new List<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 = { "api1" }
        }
    };

 

  • Configuring IdentityServer (src/IdentityServer/HostingExtensions.cs)
public static WebApplication ConfigureServices(this WebApplicationBuilder builder)
{
    builder.Services.AddIdentityServer()
        .AddInMemoryApiScopes(Config.ApiScopes)
        .AddInMemoryClients(Config.Clients);

    return builder.Build();

}


 That’s it -  IdentityServer is now configured.Run the project and then navigate to https://localhost:5001/.well-known/openid-configuration in browser


Set Up API:

  • Go to src folder and create web api and add to solution
 dotnet new webapi -n Api

                               cd ..

dotnet sln add ./src/Api/Api.csproj

  •  Add jwt bearer authentication to web (program.cs)
builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "https://localhost:5001";

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

app.UseAuthentication();

app.UseAuthorization();

   

  •  Add Authorize to the controller (WeatherForecastController.cs)

 

 [ApiController]

[Route("[controller]")]

[Authorize]

public class WeatherForecastController : ControllerBase

{

    private static readonly string[] Summaries = new[]

    {

        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"

    };


    private readonly ILogger<WeatherForecastController> _logger;


    public WeatherForecastController(ILogger<WeatherForecastController> logger)

    {

        _logger = logger;

    }


    [HttpGet(Name = "GetWeatherForecast")]

    public IEnumerable<WeatherForecast> Get()

    {

        return Enumerable.Range(1, 5).Select(index => new WeatherForecast

        {

            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),

            TemperatureC = Random.Shared.Next(-20, 55),

            Summary = Summaries[Random.Shared.Next(Summaries.Length)]

        })

        .ToArray();

    }

}

 

  •  Run the web api and access the api (http://localhost:5157/WeatherForecast) and will get 401 Error: Unauthorized

Client:

  •  Go to src and create a console project and add to the solution
dotnet new console -n Client

 cd ..

dotnet sln add ./src/Client/Client.csproj

  •  IdentityModel NuGet package to your client
dotnet add ./src/Client/Client.csproj package IdentityModel

   

  •  In Program.cs , call the identity server for access token and then with token call the api.


 using IdentityModel.Client;

using System.Text.Json;

using static System.Formats.Asn1.AsnWriter;


Console.WriteLine("Hello, World!");


// Client calling discoveryDocumentResponse

var client = new HttpClient();

var discoveryDocumentResponse = await client.GetDiscoveryDocumentAsync("https://localhost:5001");

if(discoveryDocumentResponse.IsError)

    throw new Exception(discoveryDocumentResponse.Error);




// Client getting accessToken


var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest


{

    Address = discoveryDocumentResponse.TokenEndpoint,

    ClientId = "client",

    ClientSecret = "secret",

    Scope = "api1"

});

if (tokenResponse.IsError)

{

    Console.WriteLine(tokenResponse.Error);

    return;

}


Console.WriteLine(tokenResponse.AccessToken);



// Client calling the projected API

var apiClient = new HttpClient();

apiClient.SetBearerToken(tokenResponse.AccessToken);

var response = await apiClient.GetAsync("http://localhost:5157/WeatherForecast");

if(!response.IsSuccessStatusCode) return;


var doc=JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;

Console.WriteLine(JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented=true}));

To Test the Implementation:

Run the API and IdentityServer projects separately, finally run the Client to get the following output in console

 




 

 

 

 




 


 

 

 


 


Duende IdentityServer ClientCredential flow

  Duende (Identity server)  is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core. Duende Identity Server enables the following secu...