IdentityServer — Protecting an API With ClientCredentials — Digitteck
IdentityServer — Protecting an API With ClientCredentials
dotnet·13 June 2022·4 min read

IdentityServer — Protecting an API With ClientCredentials

Here is the barebones setup needed to protect an API using the ClientCredentials grant type and IdentityServer4.

IdentityServer Project

This project has only two files — Startup.cs and Program.cs — generated as a default .NET 6 project with a single dependency:

xml
<ItemGroup>
  <PackageReference Include="IdentityServer4" Version="4.1.2" />
</ItemGroup>
csharp
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

The startup file defines clients and scopes in memory (do not use in-memory storage in production) and creates a client for the protected API with a hashed secret:

csharp
public class Startup
{
    public static IEnumerable<Client> Clients =>
        new List<Client>
        {
            new Client
            {
                ClientId = "web-api",
                AllowedGrantTypes = GrantTypes.ClientCredentials,
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },
                AllowedScopes = { "web-api-scope" },
                AllowedCorsOrigins = new List<string>
                {
                    "http://localhost:5000",
                    "https://localhost:5001"
                }
            }
        };

    public static IEnumerable<ApiScope> ApiScopes =>
        new List<ApiScope>
        {
            new ApiScope("web-api-scope", "My API")
        };

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddInMemoryApiScopes(ApiScopes)
            .AddInMemoryClients(Clients);
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            IdentityModelEventSource.ShowPII = true;
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseIdentityServer();
    }
}

Api Project

The API project must be configured to authenticate against our IdentityServer authority using the Bearer scheme. Files: Startup.cs, Program.cs, WeatherForecastController.cs.

Since multiple authentication schemes can be registered, we must specify the default with AddAuthentication(string defaultScheme) and add the Bearer scheme with .AddJwtBearer("Bearer", ...). Single dependency:

xml
<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.5" />
</ItemGroup>
csharp
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}
csharp
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddAuthorization();
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, "Bearer", options =>
            {
                options.Authority = Configuration["EndPoints:IdentityAuthority"];
                options.RequireHttpsMetadata = false;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateAudience = false
                };
            });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseCors(builder => builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Test

Three NUnit tests with both services running — the third test uses the IdentityModel library to retrieve a token from the token endpoint before making the authorized call. All tests pass:

csharp
[Test]
public async Task CallAuthorizedMethod_WithNoBearer_Fails()
{
    HttpClient apiClient = new();
    apiClient.BaseAddress = new Uri("https://localhost:5001");

    HttpRequestMessage requestMessage = new HttpRequestMessage(
        HttpMethod.Get,
        new Uri("api/authorized", UriKind.Relative));

    HttpResponseMessage response = await apiClient.SendAsync(requestMessage);
    Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized));
}

[Test]
public async Task CallNotAuthorizedMethod_Succeeds()
{
    HttpClient apiClient = new();
    apiClient.BaseAddress = new Uri("https://localhost:5001");

    HttpRequestMessage requestMessage = new HttpRequestMessage(
        HttpMethod.Get,
        new Uri("api/notauthorized", UriKind.Relative));

    HttpResponseMessage response = await apiClient.SendAsync(requestMessage);
    Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
}

[Test]
public async Task CallAuthorizedMethod_WithBearer_UsingIdentityModel_Succeeds()
{
    var identityServerClient = new HttpClient();
    DiscoveryDocumentResponse disco = await identityServerClient
        .GetDiscoveryDocumentAsync("https://localhost:6001");

    TokenResponse? tokenResponse = await identityServerClient
        .RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
        {
            Address = disco.TokenEndpoint,
            ClientId = "web-api",
            ClientSecret = "secret",
            Scope = "web-api-scope"
        });

    HttpClient apiClient = new();
    apiClient.BaseAddress = new Uri("https://localhost:5001");

    HttpRequestMessage requestMessage = new HttpRequestMessage(
        HttpMethod.Get,
        new Uri("api/authorized", UriKind.Relative));
    requestMessage.Headers.Add("Authorization", 
quot;Bearer {tokenResponse.AccessToken}"
); HttpResponseMessage response = await apiClient.SendAsync(requestMessage); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); }

Tags

.NETIdentityServerClientCredentialsAuthentication
digitteck

© 2026 Digitteck