In modern applications, securing APIs is essential. Duende IdentityServer provides a standards-based way to handle authentication and authorization in .NET
Duende IdentityServer is a highly extensible, standards-compliant framework for implementing the OpenID Connect and OAuth 2.x protocols in ASP.NET Core. It offers deep flexibility for handling authentication, authorization, and token issuance and can be adapted to fit complex custom security scenarios.

User: A user is a human that is using a registered client to access resources.

Client: A client is a piece of software that requests tokens from your IdentityServer - either for authenticating a user (requesting an identity token) or for accessing a resource (requesting an access token). A client must be first registered with your IdentityServer before it can request tokens.
While there are many different client types, e.g. web applications, native mobile or desktop applications, SPAs, server processes etc., they can all be put into two high-level categories.

IdentityResource: basically, claims that represent the user's identity, like their profile data, email, roles, etc.

ApiResource: Represents a protected API. Houses one or more ApiScopes, allowing grouped behavior such as audience enforcement.

ApiScope: a discrete permission or level of access that a client can request for an API . Defines a granular permission clients can request for accessing APIs. Includes metadata like name, display, description, and optional claims.

👉Think of ApiScope like a key that gives access to a specific door (API endpoint). ApiResource is the building that contains one or more doors

IdentityServer Quickstarts:
-create a webapi project

dotnet new web -n MyIdentityServer

 

Add the package reference

add nuget Duende.IdentityServer


change program.cs

var builder = WebApplication.CreateBuilder(args)

//ConfigureServices----------------------------------------
builder.Services.AddIdentityServer()
       .AddInMemoryIdentityResources(Config.IdentityResources)
       .AddInMemoryApiScopes(Config.ApiScopes)
       .AddInMemoryClients(Config.Clients)
       .AddLicenseSummary();

//ConfigurePipeline----------------------------------------
var app = builder.Build();


app.UseStaticFiles();
app.UseRouting();

app.UseIdentityServer();

app.Run();


-Add this class

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

    public static IEnumerable<ApiScope> ApiScopes =>
    [
        new ApiScope(name: "api1", displayName: "My API")
    ];

    public static IEnumerable<Client> Clients =>
    [
        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" }
        }
    ];
}

 


-Run project and go to address : 
https://localhost:5001/.well-known/openid-configuration
This returns IdentityServer metadata in JSON format, which confirms the server is running correctly.
 

 


Test The Identity Endpoint:

-create An API Project
-add nuget

Microsoft.AspNetCore.Authentication.JwtBearer


           
-change program.cs

using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication()
    .AddJwtBearer(options =>
    {
        options.Authority = "https://localhost:5001";
        options.TokenValidationParameters.ValidateAudience = false;
    });

builder.Services.AddAuthorization();


var app = builder.Build();

app.UseHttpsRedirection();

app.MapGet("identity", 
         (ClaimsPrincipal user) => user.Claims.Select(c => new { c.Type, c.Value }))
    .RequireAuthorization();

app.Run();

 

 

Create The Client Project

-create a console project

using Duende.IdentityModel.Client;
using System.Text.Json;

// discovery endpoints from metadata
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");
if (disco.IsError)
{
    Console.WriteLine(disco.Error);
    Console.WriteLine(disco.Exception);
    return;
}


//Request A Token From IdentityServer
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
    Address = disco.TokenEndpoint,
    ClientId = "client",
    ClientSecret = "secret",
    Scope = "api1"
});

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

Console.WriteLine(tokenResponse.AccessToken);


//Calling The API
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken!); // AccessToken is always non-null when IsError is false

var response = await apiClient.GetAsync("https://localhost:7026/identity");
if (!response.IsSuccessStatusCode)
{
    Console.WriteLine(response.StatusCode);
    return;
}

var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
Console.WriteLine(JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true }));

Console.ReadKey();