Skip to content
Ashish.
All posts
Diagram illustrating the Keycloak Admin API client generation process from OpenAPI spec to TypeScript code.
6 min readDevelopmentBackend Developers, Platform Engineers#keycloak#openapi#code-generation#typescript#api-clients#backend#security#automation

Generating a Keycloak Admin API Client From OpenAPI

Learn how to generate a typed Keycloak Admin API client from OpenAPI specifications to improve type safety and reduce boilerplate code.

By Ashish Kumar

Keycloak’s Admin API is a REST interface for managing realms, clients, users, and roles. However, its documentation has historically been fragmented, and the raw HTTP endpoints lack the type safety modern TypeScript applications expect. Developers often resort to writing manual wrapper functions or using generic HTTP clients, leading to boilerplate, runtime errors, and drift between the client code and Keycloak’s evolving API.

The solution is to treat Keycloak’s Admin API as a contract defined by an OpenAPI specification. By extracting this specification and running a code generator, you can produce a fully typed, self-documenting client library. This approach shifts the burden of API compliance from your hand-written code to the generation pipeline, ensuring that any change in Keycloak’s API structure is caught at compile time.

The Source of Truth: The OpenAPI Specification

The first step in this mechanism is obtaining the OpenAPI 3.0 specification that describes Keycloak’s Admin API. Unlike some services that expose a live Swagger UI endpoint by default, Keycloak’s OpenAPI spec is often bundled with the server or must be exported manually.

In recent versions of Keycloak (21+), the Admin API is exposed via a standardized REST interface. You can often find the OpenAPI JSON specification by accessing the Swagger UI if it is enabled, or by locating the openapi.json file within the Keycloak distribution or related GitHub repositories.

For the purpose of this guide, assume we have a file named keycloak-admin-openapi.json. This file contains the schemas for resources like UserRepresentation, ClientRepresentation, and the operations (GET, POST, PUT, DELETE) that manipulate them.

{
  "openapi": "3.0.1",
  "info": {
    "title": "Keycloak Admin API",
    "version": "21.0.0"
  },
  "paths": {
    "/admin/realms/{realm}/users": {
      "get": {
        "summary": "Get all users",
        "operationId": "getUsers",
        "parameters": [
          {
            "name": "realm",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/UserRepresentation" }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "UserRepresentation": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "username": { "type": "string" },
          "email": { "type": "string" }
        }
      }
    }
  }
}

This JSON structure is the contract. It defines the shape of the data (UserRepresentation) and the operations (getUsers). Our goal is to translate this static description into dynamic TypeScript code.

A clean, technical diagram showing a JSON file labeled 'OpenAPI Spec' being fed into a gear icon labeled 'Code Generator'. The style is minimalist vector art, blue and grey palette, white background, clear labels.

Generating the Client

We use openapi-typescript to convert the JSON specification into TypeScript definitions. This tool parses the OpenAPI schema and generates type definitions for every model and for the request and response shapes of every endpoint, though it does not generate a callable client — that layer you still write yourself using the generated types.

Install the tool globally or as a dev dependency:

npm install -D openapi-typescript

Run the generator against the Keycloak OpenAPI file:

npx openapi-typescript keycloak-admin-openapi.json -o src/keycloak-api.d.ts

The output (src/keycloak-api.d.ts) will contain a series of type definitions. For the example above, it generates:

export interface components {
  schemas: {
    UserRepresentation: {
      id?: string;
      username?: string;
      email?: string;
    };
  };
}

You then define your own client interface on top of these generated types:

export type UserRepresentation = components["schemas"]["UserRepresentation"];
 
export interface KeycloakAdminApi {
  getUsers(realm: string): Promise<UserRepresentation[]>;
  // ... other methods
}

This step is crucial because it creates a type-safe layer. If Keycloak adds a new field to UserRepresentation in a future version, the generated types will reflect that. If your code tries to access a field that no longer exists or has changed type, TypeScript will throw a compile-time error, preventing runtime failures in production.

Implementing the Client Wrapper

The generated types define the shape of the API, but they do not include the HTTP execution logic. To make the client functional, we need a thin wrapper that handles authentication and HTTP requests.

Create a client class that implements the KeycloakAdminApi interface you defined against the generated types. This wrapper uses a standard HTTP client like axios or node-fetch to execute requests.

import axios from 'axios';
import { KeycloakAdminApi, UserRepresentation } from './keycloak-api';
 
class KeycloakClient implements KeycloakAdminApi {
  private baseUrl: string;
  private accessToken: string;
 
  constructor(baseUrl: string, accessToken: string) {
    this.baseUrl = baseUrl;
    this.accessToken = accessToken;
  }
 
  async getUsers(realm: string): Promise<UserRepresentation[]> {
    const response = await axios.get(
      `${this.baseUrl}/admin/realms/${realm}/users`,
      {
        headers: {
          Authorization: `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json',
        },
      }
    );
    return response.data;
  }
 
  // Implement other methods from the generated interface...
}

By implementing the interface, TypeScript enforces that all required methods are present. If Keycloak adds a new endpoint like deleteUser, the compiler will complain that KeycloakClient does not implement deleteUser, prompting you to update the wrapper.

Usage in Application Code

With the client implemented, you can use it in your backend services with full type safety.

import { KeycloakClient } from './keycloak-client';
 
async function main() {
  const client = new KeycloakClient(
    'http://localhost:8080',
    'your-admin-access-token'
  );
 
  const users = await client.getUsers('my-realm');
 
  // TypeScript knows the structure of each user
  users.forEach(user => {
    console.log(`User: ${user.username}, Email: ${user.email}`);
  });
}
 
main();

If you try to access user.age (which does not exist in the schema), TypeScript will throw an error: Property 'age' does not exist on type 'UserRepresentation'. This prevents bugs caused by incorrect assumptions about the API structure.

Versioning and Maintenance

Effective versioning of the generated client is crucial to maintaining system stability. When Keycloak releases a new version, you must regenerate the client.

  1. Update the keycloak-admin-openapi.json file with the new specification.
  2. Re-run the openapi-typescript command.
  3. Update the KeycloakClient wrapper to implement any new or changed methods.
  4. Run your test suite to ensure compatibility, ensuring that security tokens are handled correctly during regeneration.

This process is automated in CI/CD pipelines. You can configure a script that checks for updates to the OpenAPI spec and regenerates the client if changes are detected. This ensures that your client library is always in sync with the Keycloak server, leveraging automation to reduce manual effort.

Tradeoffs

Generating a client from OpenAPI has advantages and disadvantages.

Advantages:

  • Type Safety: Compile-time checks prevent API misuse.
  • Reduced Boilerplate: No need to manually write HTTP request code for every endpoint.
  • Documentation: The generated types serve as living documentation.
  • Consistency: The client always matches the server’s API structure.

Disadvantages:

  • Regeneration Overhead: Every API change requires regeneration and potential code updates.
  • Complexity: The generated code can be large and complex, making debugging difficult.
  • Version Lock-in: You are tied to the specific version of the OpenAPI spec used for generation.

Despite these drawbacks, the benefits of type safety and reduced boilerplate outweigh the costs for most backend applications. The maintenance overhead is minimal compared to the risk of runtime errors and the effort of manual wrapper development.

Conclusion

Generating a Keycloak Admin API client from OpenAPI specifications is an effective strategy for improving type safety and reducing boilerplate. By treating the OpenAPI spec as the source of truth and using code generation tools, you can create a client library that is always in sync with Keycloak’s API. This approach simplifies development, prevents bugs, and makes your codebase more maintainable.

Start by exporting the OpenAPI spec from your Keycloak instance, run the generator, and implement a thin HTTP wrapper. The result is a robust, typed client that integrates smoothly into your TypeScript backend as a reliable admin api client.

Related posts