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:

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.

  1. In the Keycloak console, navigate to Realms and click Create Realm.
  2. 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.

  1. In the Clients section, click Create.
  2. 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.
  3. After saving, go to the Credentials tab, and copy the client secret. This will be needed to configure ASP.NET later.
  4. 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.

  1. Go to Client Scopes and click Create.
  2. Create a scope named blogs:view, and enable Include in token scope.
  3. 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

  1. Go to Clients, select client-service, and go to Client Scopes.
  2. Click Add Client Scope and select blogs:view as 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:

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

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: