ASP.NET Identity Management with KeyCloak
Originally published on Medium · November 14, 2024
Keycloak keeps coming up whenever a team wants identity and access management without tying themselves to a paid SaaS provider. It's open source, it's flexible, and once you've wired it into one ASP.NET app the pattern repeats easily across others. Here's how I set it up end to end — Keycloak, MariaDB, and an ASP.NET API talking to each other with proper token-based authorization.
Everything runs locally through Docker Compose, which handles spinning up ASP.NET, Keycloak, and MariaDB together. By the end you'll have a working local setup where the ASP.NET app authenticates and authorizes requests using tokens issued by Keycloak.
Step 1: Spin Up Keycloak with Docker Compose
First, let's set up Keycloak with a MariaDB database for persistent storage so that any configurations, users, or realms you create will be retained across container restarts.
1.1 Create a docker-compose.yml file
In your project directory, create a docker-compose.yml file and add the following configuration:
version: '3.7'
services:
# Define the MariaDB database service
mariadb:
image: mariadb:latest #MariaDB latest image
container_name: mariadb
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: keycloakdb
MYSQL_USER: keycloakuser
MYSQL_PASSWORD: keycloakpassword
restart: always
networks:
- keycloak-network # Connect the MariaDB container to the specified network
volumes:
- mariadb_data:/var/lib/mysql # Persist database data using a named volume
# Define the Keycloak service
keycloak:
image: quay.io/keycloak/keycloak:latest #KeyCloak latest image
container_name: keycloak
environment:
KC_DB: mariadb
KC_DB_URL_HOST: mariadb # Set the MariaDB host for Keycloak
KC_DB_USERNAME: keycloakuser # Set the MariaDB user for Keycloak
KC_DB_PASSWORD: keycloakpassword # Set the MariaDB password for Keycloak
KC_DB_DATABASE: keycloakdb # Set the MariaDB database name for Keycloak
KEYCLOAK_ADMIN: admin # Set the admin username for Keycloak
KEYCLOAK_ADMIN_PASSWORD: admin # Set the admin password for Keycloak
command: ["start-dev"] # Start Keycloak in development mode
ports:
- 8080:8080
networks:
- keycloak-network # Connect the Keycloak container to the same network as MariaDB
depends_on:
- mariadb # Ensure the MariaDB service starts before Keycloak
# Define the network for communication between services
networks:
keycloak-network:
driver: bridge # Use a bridge network to isolate Keycloak and MariaDB
# Define a named volume for MariaDB data persistence
volumes:
mariadb_data:
1.2 Start Keycloak and MariaDB
In the same directory as your docker-compose.yml file, run the following command to start the containers:
docker-compose up
This command will start Keycloak on http://localhost:8080. Use the default admin credentials to log in:
- Username:
admin - Password:
admin
With this setup, you have a persistent Keycloak environment backed by MariaDB. All configurations will be saved in the database, so you won't lose them even after restarting the containers.
Step 2: Create a New Realm in Keycloak
Once logged into the Keycloak admin console (http://localhost:8080), create a new realm. Realms allow you to isolate users, clients, and configurations.
- In the Keycloak console, navigate to Realms and click Create Realm.
- Name the realm something like
local-dev, then click Save.
Step 3: Create Clients in Keycloak
We'll set up two clients in Keycloak — one for the ASP.NET application and another representing the service that will call this application.
- In the Clients section, click Create.
- For the ASP.NET application, configure the following settings:
- Client ID:
blogs - Client Protocol:
openid-connect - Capabilities: Enable Client authentication and Service accounts roles, then save.
- Client ID:
- After saving, go to the Credentials tab, and copy the client secret. This will be needed to configure ASP.NET later.
- Repeat the steps to create another client (e.g.,
client-service) that will represent external applications calling your ASP.NET application.
Step 4: Add Scopes for Precise Access Control
Scopes in Keycloak allow granular control over access to different parts of the application. In this example, we'll create a scope for our blogs API.
- Go to Client Scopes and click Create.
- Create a scope named
blogs:view, and enable Include in token scope. - Save the scope and navigate to the Mappers section to configure audience mapping:
- Select Audience as the mapper type.
- Name it something like
blogs-audience. - In Included Client Audience, select
blogs.
Assign Scope to Clients
- Go to Clients, select
client-service, and go to Client Scopes. - Click Add Client Scope and select
blogs:viewas Default.
Test the Scopes
To verify that your scope has been added correctly, make an HTTP request to Keycloak to retrieve a token.
Example curl command:
curl --location 'http://localhost:8080/realms/local-dev/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=client-service' \
--data-urlencode 'client_secret=***'
This command should return a token containing the scopes assigned to the client.
Step 5: Wire ASP.NET Up to Keycloak
Now that Keycloak is configured, it's time to connect your ASP.NET application.
5.1 Install the Required NuGet Package
Install the JwtBearer authentication package to enable token-based authentication in your ASP.NET application:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
5.2 Configure JWT Authentication in ASP.NET
In the Program.cs file, configure the authentication to use Keycloak's OpenID Connect endpoint:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "http://localhost:8080/realms/local-dev";
options.Audience = "blogs";
options.RequireHttpsMetadata = false; // Disable for local dev
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidateIssuer = true
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ReadAccess", policy => policy.RequireAssertion(context =>
context.User.HasClaim(c => c.Type == "scope" && c.Value.Contains("blogs:view"))
));
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
In this configuration:
Authorityis the URL of the Keycloak realm.Audienceis the client ID in Keycloak that represents your ASP.NET application. In our case,blogs.- A policy called
ReadAccessis created to restrict access to endpoints that require theblogs:viewscope.
5.3 Create a Controller with Authorization
Now that our authentication and authorization are set up, we need to add a controller in our ASP.NET application to test secure access with Keycloak. In this example, we'll create a simple BlogController that uses an authorization policy to restrict access to users with the appropriate scope.
5.4 Define a Controller with Policy-Based Authorization
In your ASP.NET application, add a new controller file called BlogController.cs in the Controllers folder. This controller will be accessible only to users with the ReadAccess policy, which we defined earlier to require the blogs:view scope.
Here's the code for the BlogController:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Blogs.Controllers;
[ApiController]
[Route("[controller]")]
public class BlogController() : ControllerBase
{
[Authorize(Policy = "ReadAccess")]
[HttpGet("secure-data")]
public IActionResult GetSecureData()
{
return Ok("You have read access!");
}
}
Explanation of the Code
[Authorize(Policy = "ReadAccess")]: This attribute specifies that access to theGetSecureDataendpoint is restricted to users who satisfy theReadAccessauthorization policy, which requires theblogs:viewscope.[HttpGet("secure-data")]: Defines a GET endpoint at/Blog/secure-data, which is protected by Keycloak-based authorization.GetSecureData: This action method returns a success message if the user is authorized.
5.5 Test the Secure Endpoint
To test this endpoint, obtain a token from Keycloak that includes the blogs:view scope, and pass it in the Authorization header of your HTTP request to the /Blog/secure-data endpoint. For example:
curl -X GET 'http://localhost:5000/Blog/secure-data' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
If the token includes the required scope, you should receive the message: You have read access!. Otherwise, you'll get a 403 Forbidden error, indicating that access is restricted.
Wrapping Up
You have now configured Keycloak to manage authentication and authorization for your ASP.NET application using Docker Compose. With this setup:
- Keycloak and MariaDB are running in Docker, providing a persistent and isolated authentication system.
- An ASP.NET application is set up to use JWT Bearer tokens, enabling secure, scope-based access.
- This integration allows for seamless user authentication and access control across your application, all powered by Keycloak.