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:
An Identity Server
An API that requires authentication
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 authenticationAllowedGrantTypes = GrantTypes.ClientCredentials,// secret for authenticationClientSecrets ={new Secret("secret".Sha256())},// scopes that client has access toAllowedScopes = { "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