IdentityServer4 — Customise Part 1 — Replacing InMemory Clients — Digitteck
IdentityServer4 — Customise Part 1 — Replacing InMemory Clients
dotnet·21 January 2020·4 min read

IdentityServer4 — Customise Part 1 — Replacing InMemory Clients

IdentityServer4 ships with a quickstart UI that stores clients, resources, and users in memory — ideal for getting started. When you need to move client configuration into a real database, IS4 provides a clean extension point: IClientStore. This article replaces AddInMemoryClients with a custom MongoDB store.

QuickStart Default

The quickstart registers everything from a static Config class. The InMemoryClientStore that backs IClientStore simply holds this list in a private field:

csharp
// The QuickStart startup registers test clients from a static Config class:
services.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddInMemoryClients(Config.GetClients())       // ← static list, not from a database
    .AddInMemoryIdentityResources(Config.GetIdentityResources())
    .AddInMemoryApiResources(Config.GetApiResources());

Replace with AddClientStore

IS4 provides AddClientStore<T> to swap in any IClientStore implementation. The store is resolved from the DI container so it can receive injected dependencies:

csharp
// Replace AddInMemoryClients with a custom store:
services.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddClientStore<MongoClientStore>()            // ← resolved from DI, can inject IMongoDatabase
    .AddInMemoryIdentityResources(Config.GetIdentityResources())
    .AddInMemoryApiResources(Config.GetApiResources());

IClientStore

The interface has a single method. IS4 calls it whenever it needs to validate a client or build a token:

csharp
// IClientStore — the contract IdentityServer4 calls when it needs a client
public interface IClientStore
{
    Task<Client> FindClientByIdAsync(string clientId);
}

Custom MongoDB Store

csharp
// Custom implementation — reads clients from MongoDB
public class MongoClientStore : IClientStore
{
    private readonly IMongoCollection<Client> _collection;

    public MongoClientStore(IMongoDatabase database)
        => _collection = database.GetCollection<Client>("clients");

    public async Task<Client> FindClientByIdAsync(string clientId)
        => await _collection
            .Find(c => c.ClientId == clientId)
            .FirstOrDefaultAsync();
}

Protected API

The consuming API project protects its endpoints with JwtBearer, pointing at the IS4 server as the authority:

csharp
// Protected API project — configure JWT Bearer with IS4 as authority
services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "https://localhost:5001"; // IS4 endpoint
        options.Audience  = "api1";
    });

// Controller
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    public IEnumerable<WeatherForecast> Get() => _forecasts;
}

Console Client

The IdentityModel package handles discovery and token exchange. Three steps: discover endpoints, exchange client credentials for a token, call the protected API:

csharp
// Console app — exchange credentials for a token then call the protected API
using IdentityModel.Client;

// Discover the IS4 endpoints
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");

// Request a client_credentials token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(
    new ClientCredentialsTokenRequest
    {
        Address      = disco.TokenEndpoint,
        ClientId     = "client",
        ClientSecret = "secret",
        Scope        = "api1"
    });

// Call the protected API with the bearer token
client.SetBearerToken(tokenResponse.AccessToken);
var response = await client.GetAsync("https://localhost:6001/weatherforecast");
var content  = await response.Content.ReadAsStringAsync();

Tags

.NETASP.NET CoreIdentityServer4OAuth2OpenID Connect
digitteck

© 2026 Digitteck