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

 




 

 

 

 




 


 

 

 


 


No comments:

Post a Comment

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...