IdentityServer4 — Part 6 — Protecting API — Client Credentials Example — Digitteck
IdentityServer4 — Part 6 — Protecting API — Client Credentials Example
dotnet·18 November 2019·5 min read

IdentityServer4 — Part 6 — Protecting API — Client Credentials Example

The Client Credentials flow is the simplest OAuth 2.0 flow for machine-to-machine authorization. There is no user context — a client application exchanges its own credentials for an access token and uses that token to call a protected API.

Steps

  1. Register a client (who accesses the API) and an API resource in the Identity Server
  2. The client calls the token endpoint with its client_id and client_secret
  3. The Identity Server returns an access token
  4. The client sends the access token as a Bearer header when calling the protected API
  5. The API validates the token against the Identity Server and authorizes the request

A. Identity Server — Config

Define the API resource and the client in memory. In production, use a database-backed client and resource store (covered in the Customizing IdentityServer4 article):

csharp
// Config.cs — in-memory clients and API resources for the Identity Server
public static class Config
{
    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource("api1", "My API")
            {
                Scopes = { new Scope("api1") }
            }
        };
    }

    public static IEnumerable<Client> GetClients()
    {
        return new List<Client>
        {
            new Client
            {
                ClientId          = "console_client",
                AllowedGrantTypes = GrantTypes.ClientCredentials,
                ClientSecrets     = { new Secret("my_secret".Sha256()) },
                AllowedScopes     = { "api1" },
            }
        };
    }
}

B. Identity Server — Startup

Wire up the Identity Server middleware in a standard ASP.NET Core Web API project:

csharp
// Startup.cs — Identity Server configuration (.NET Core 3)
public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentityServer()
        .AddDeveloperSigningCredential()      // dev only; use real cert in production
        .AddInMemoryApiResources(Config.GetApiResources())
        .AddInMemoryClients(Config.GetClients());
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();
    app.UseIdentityServer();
}

C. Protected API

A separate ASP.NET Core project acts as the resource server. It adds JwtBearer authentication, specifying the Identity Server as the authority and api1 as the required audience. Every incoming token is automatically validated on each request:

csharp
// The protected API project — adds JWT Bearer authentication
// Startup.ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication("Bearer")
        .AddJwtBearer("Bearer", options =>
        {
            options.Authority            = "https://localhost:5001"; // Identity Server URL
            options.RequireHttpsMetadata = false;
            options.Audience             = "api1";                   // must match ApiResource name
        });

    services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints => endpoints.MapControllers());
}

// Controller — require authentication via [Authorize]
[ApiController]
[Route("[controller]")]
[Authorize(AuthenticationSchemes = "Bearer")]
public class ValuesController : ControllerBase
{
    [HttpGet]
    public IActionResult Get() => Ok(new[] { "value1", "value2" });
}

D. Console Client

The console app uses the IdentityModel package to discover the token endpoint, exchange the client credentials for an access token, and then call the protected API using that token as a Bearer header:

csharp
// Console client — exchange client credentials for a token, then call the API
// NuGet: IdentityModel

static async Task Main()
{
    using var httpClient = new HttpClient();

    // 1. Discover endpoints from the Identity Server
    var disco = await httpClient.GetDiscoveryDocumentAsync(
        new DiscoveryDocumentRequest
        {
            Address  = "https://localhost:5001",
            Policy   = { RequireHttps = false }
        });

    if (disco.IsError)
    {
        Console.WriteLine(
quot;Discovery error: {disco.Error}"
); return; } // 2. Request an access token using client credentials var tokenResponse = await httpClient.RequestClientCredentialsTokenAsync( new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "console_client", ClientSecret = "my_secret", Scope = "api1", }); if (tokenResponse.IsError) { Console.WriteLine(
quot;Token error: {tokenResponse.Error}"
); return; } Console.WriteLine(
quot;Access token: {tokenResponse.AccessToken}"
); // 3. Call the protected API with the Bearer token var apiClient = new HttpClient(); apiClient.SetBearerToken(tokenResponse.AccessToken); var response = await apiClient.GetAsync("https://localhost:6001/values"); if (!response.IsSuccessStatusCode) { Console.WriteLine(
quot;API error: {response.StatusCode}"
); return; } var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(
quot;Response: {content}"
); }

Tags

.NETIdentityServer4OAuth2OpenID Connect
digitteck

© 2026 Digitteck