Skip to content
Back to skills

Keycloak

ASecurity

Keycloak is an open-source identity and access management server that gives applications single sign-on with OAuth 2.0, OpenID Connect and SAML 2.0. Use when a user asks to set up or self-host Keycloak, add SSO or social login, create a realm or client, connect LDAP or Active Directory, require MFA, log in to a Next.js app with Keycloak, or manage users with kcadm or the Admin REST API.

  • 142 stars
  • 0 votes
  • 0 copies
  • 6 views
  • Added May 29, 2026
developmenttypescriptrustgobashsqlnextjsdockerkubernetesgitapi

Works with

  • terminal
  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill keycloak --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Keycloak?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Keycloak
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-keycloak/badge)](https://www.skillsdirectory.com/skills/terminalskills-keycloak)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: keycloak
description: >-
  Keycloak is an open-source identity and access management server that gives
  applications single sign-on with OAuth 2.0, OpenID Connect and SAML 2.0. Use
  when a user asks to set up or self-host Keycloak, add SSO or social login,
  create a realm or client, connect LDAP or Active Directory, require MFA,
  log in to a Next.js app with Keycloak, or manage users with kcadm or the
  Admin REST API.
license: Apache-2.0
compatibility: 'Keycloak 26.x; Docker or Podman for the container image, PostgreSQL or another supported database in production'
metadata:
  author: terminal-skills
  version: 1.1.0
  category: devops
  tags:
    - identity
    - authentication
    - sso
    - oidc
    - self-hosted
  repository: https://github.com/keycloak/keycloak
---

# Keycloak — Open-Source Identity and Access Management

## Overview

Keycloak is an open-source identity and access management server, a CNCF incubating project that started at Red Hat. It handles single sign-on (SSO), OAuth 2.0, OpenID Connect, SAML 2.0, user federation (LDAP/Active Directory), social login, multi-factor authentication and fine-grained authorization, so applications delegate login to it instead of storing passwords. This skill targets Keycloak 26 (current: 26.8.0). Two things changed from the releases most tutorials describe: the first admin is created with `KC_BOOTSTRAP_ADMIN_USERNAME` / `KC_BOOTSTRAP_ADMIN_PASSWORD` (`KEYCLOAK_ADMIN*` is deprecated), and the hostname option takes a full URL.

## Instructions

### Setup

```yaml
# docker-compose.yml — Keycloak with PostgreSQL behind a TLS-terminating reverse proxy
services:
  keycloak:
    image: quay.io/keycloak/keycloak:26.8.0
    command: start
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: ${KC_DB_PASSWORD}
      KC_BOOTSTRAP_ADMIN_USERNAME: bootstrap-admin
      KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD}
      KC_HOSTNAME: https://auth.ledgerline.dev      # public URL, scheme included
      KC_HTTP_ENABLED: "true"                       # the proxy terminates TLS and talks HTTP to :8080
      KC_PROXY_HEADERS: xforwarded                  # the proxy must set and overwrite X-Forwarded-*
      KC_HEALTH_ENABLED: "true"                     # /health/ready on the management port 9000
    ports:
      - "127.0.0.1:8080:8080"
    depends_on:
      - postgres

  postgres:
    image: postgres:17
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
    volumes:
      - pg_data:/var/lib/postgresql/data

volumes:
  pg_data:
```

`start` is production mode: it refuses to run without a hostname and, unless `KC_HTTP_ENABLED` is set, without a TLS certificate (`KC_HTTPS_CERTIFICATE_FILE` / `KC_HTTPS_CERTIFICATE_KEY_FILE`). `start-dev` relaxes all of that and is for local use only. The bootstrap admin is temporary: sign in, create a permanent admin user in the `master` realm, then delete the bootstrap one.

### Realm Configuration

```markdown
## Key Concepts

### Realm
- Isolated namespace for users, clients, roles
- Each realm has its own login page, user database, settings
- `master` is only for administering Keycloak; create a separate realm for applications

### Clients
- Applications that authenticate via Keycloak
- Types: public (SPA or mobile app, "Client authentication" off, PKCE) and
  confidential (backend, "Client authentication" on, has a secret)
- Configure redirect URIs, web origins (CORS), token lifetimes

### Roles
- Realm roles: global across all clients (admin, user, moderator)
- Client roles: scoped to a specific application (api:read, api:write)
- Composite roles: combine multiple roles into one

### Identity Providers
- Social: Google, GitHub, Facebook, LinkedIn, Microsoft
- Enterprise: SAML or OIDC (Okta, Microsoft Entra ID); LDAP and Active Directory
  are configured under User federation, not as identity providers
- Custom: any OIDC/SAML 2.0 provider

### Authentication Flows
- Username/password + OTP (TOTP/HOTP)
- WebAuthn (passkeys, security keys)
- Custom flows (conditional OTP, required actions)
```

Everything in the Admin Console can be scripted with `kcadm.sh`, shipped in the server's `bin` directory (`/opt/keycloak/bin` in the image):

```bash
kcadm.sh config credentials --server http://localhost:8080 --realm master --user bootstrap-admin
# prompts for the password, or reads it from KC_CLI_PASSWORD
kcadm.sh create realms -s realm=ledgerline -s enabled=true
kcadm.sh create roles -r ledgerline -s name=editor
kcadm.sh create users -r ledgerline -s username=maria.ortiz -s email=maria.ortiz@ledgerline.dev -s enabled=true
kcadm.sh add-roles -r ledgerline --uusername maria.ortiz --rolename editor
```

### Client Integration (Next.js)

Create a confidential OpenID Connect client with the redirect URI `https://app.ledgerline.dev/api/auth/callback/keycloak`, then:

```typescript
// src/auth.ts — Keycloak OIDC integration with Auth.js v5 (npm install next-auth@beta; `latest` is still v4)
import NextAuth from "next-auth";
import Keycloak from "next-auth/providers/keycloak";

declare module "next-auth" { interface Session { accessToken?: string } }

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Keycloak({
      clientId: process.env.AUTH_KEYCLOAK_ID!,
      clientSecret: process.env.AUTH_KEYCLOAK_SECRET!,
      // The issuer includes the realm: https://auth.ledgerline.dev/realms/ledgerline
      issuer: process.env.AUTH_KEYCLOAK_ISSUER!,
    }),
  ],
  callbacks: {
    async jwt({ token, account }) {
      if (account) {
        token.accessToken = account.access_token;
        token.refreshToken = account.refresh_token;
        token.idToken = account.id_token;
      }
      return token;
    },
    async session({ session, token }) {
      session.accessToken = token.accessToken as string;
      return session;
    },
  },
});
```

With those three variable names the provider also works as a bare `providers: [Keycloak]`. Any other OIDC library needs only the discovery document at `<issuer>/.well-known/openid-configuration`.

### Admin API

```typescript
// Keycloak Admin REST API — manage users programmatically
const KEYCLOAK_URL = process.env.KEYCLOAK_URL;   // https://auth.ledgerline.dev
const REALM = process.env.KEYCLOAK_REALM;        // ledgerline

// A confidential client in the same realm with "Service account roles" enabled and the
// realm-management client roles manage-users and view-realm (reading a role needs the latter).
// (admin-cli is a public client: it has no secret and cannot use the client_credentials grant.)
async function getAdminToken(): Promise<string> {
  const res = await fetch(
    `${KEYCLOAK_URL}/realms/${REALM}/protocol/openid-connect/token`,
    {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        grant_type: "client_credentials",
        client_id: process.env.KEYCLOAK_ADMIN_CLIENT_ID!,
        client_secret: process.env.KEYCLOAK_ADMIN_CLIENT_SECRET!,
      }),
    },
  );
  if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
  const { access_token } = await res.json();
  return access_token;
}

async function createUser(userData: {
  username: string;
  email: string;
  firstName: string;
  lastName: string;
}): Promise<string> {
  const token = await getAdminToken();
  const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
  const res = await fetch(`${KEYCLOAK_URL}/admin/realms/${REALM}/users`, {
    method: "POST",
    headers,
    body: JSON.stringify({ ...userData, enabled: true }),
  });
  if (res.status !== 201) throw new Error(`Create user failed: ${res.status}`); // 409 = already exists
  const userId = res.headers.get("Location")!.split("/").pop()!;

  // No password is set by the admin: the user gets an email with a link to choose one
  // (needs SMTP settings under Realm settings → Email)
  await fetch(`${KEYCLOAK_URL}/admin/realms/${REALM}/users/${userId}/execute-actions-email`, {
    method: "PUT",
    headers,
    body: JSON.stringify(["UPDATE_PASSWORD", "VERIFY_EMAIL"]),
  });
  return userId;
}

async function assignRole(userId: string, roleName: string) {
  const token = await getAdminToken();
  // Get role
  const rolesRes = await fetch(
    `${KEYCLOAK_URL}/admin/realms/${REALM}/roles/${roleName}`,
    { headers: { Authorization: `Bearer ${token}` } },
  );
  if (!rolesRes.ok) throw new Error(`Role lookup failed: ${rolesRes.status}`); // 403 = view-realm missing
  const role = await rolesRes.json();

  // Assign to user
  await fetch(
    `${KEYCLOAK_URL}/admin/realms/${REALM}/users/${userId}/role-mappings/realm`,
    {
      method: "POST",
      headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
      body: JSON.stringify([role]),
    },
  );
}
```

### Installation

```bash
# Docker (development): dev mode, embedded database, HTTP on localhost only
docker run --name keycloak -p 127.0.0.1:8080:8080 \
  -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD="$KC_ADMIN_PASSWORD" \
  quay.io/keycloak/keycloak:26.8.0 start-dev

# Production: a stock image is started with `start` (see the compose file above);
# `start --optimized` works only in an image where `kc.sh build` already ran

# Kubernetes: the Keycloak Operator, then a Keycloak custom resource
kubectl create namespace keycloak
kubectl apply -k 'github.com/keycloak/keycloak-k8s-resources/kubernetes?ref=26.8.0'

# Move a realm between environments (server stopped, same database options as the server)
/opt/keycloak/bin/kc.sh export --dir /opt/keycloak/data/export --realm ledgerline
/opt/keycloak/bin/kc.sh import --dir /opt/keycloak/data/export
```

## Examples

### Example 1: Local Keycloak with a realm and a client for a web app

User: "Run Keycloak locally and give me a client ID and secret for our Next.js app on localhost:3000."

```bash
docker run -d --name keycloak -p 127.0.0.1:8080:8080 \
  -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD="$KC_ADMIN_PASSWORD" \
  quay.io/keycloak/keycloak:26.8.0 start-dev
until curl -sf -o /dev/null http://localhost:8080/realms/master; do sleep 2; done   # start-up takes 10-30 s

kc() { docker exec -e KC_CLI_PASSWORD="$KC_ADMIN_PASSWORD" keycloak /opt/keycloak/bin/kcadm.sh "$@"; }
kc config credentials --server http://localhost:8080 --realm master --user admin
kc create realms -s realm=ledgerline -s enabled=true
CID=$(kc create clients -r ledgerline -s clientId=ledgerline-web -s publicClient=false \
  -s 'redirectUris=["http://localhost:3000/api/auth/callback/keycloak"]' -i)
kc get clients/$CID/client-secret -r ledgerline
```

`-i` makes `create` print only the new client's internal ID, and the last command prints a JSON object whose `value` field is the client secret. Put the value into `.env.local` as `AUTH_KEYCLOAK_SECRET`, together with `AUTH_KEYCLOAK_ID=ledgerline-web` and `AUTH_KEYCLOAK_ISSUER=http://localhost:8080/realms/ledgerline`. `http://localhost:8080/realms/ledgerline/.well-known/openid-configuration` now returns the discovery document, and the Admin Console is at `http://localhost:8080/admin`.

### Example 2: Fix "redirect loops and http:// links" behind a reverse proxy

User: "Keycloak runs behind nginx on https://auth.ledgerline.dev, but the login page loads assets from http:// and the admin console spins forever."

Keycloak builds every URL from its hostname settings, so tell it its public address and that a proxy is in front:

```bash
/opt/keycloak/bin/kc.sh start --hostname https://auth.ledgerline.dev \
  --http-enabled true --proxy-headers xforwarded
```

and make nginx pass the original scheme and host (`proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`). The `issuer` in `https://auth.ledgerline.dev/realms/ledgerline/.well-known/openid-configuration` then starts with `https://auth.ledgerline.dev`. To see what Keycloak derives from a request, start once with `--hostname-debug=true` and open `/realms/ledgerline/hostname-debug`.

## Guidelines

1. **Realm per environment** — Separate realms for dev/staging/production; export/import configs between them
2. **Confidential clients for backends** — Use client secret authentication; never expose secrets in frontend apps
3. **RBAC with roles** — Map business roles (admin, editor, viewer) to Keycloak realm/client roles
4. **Social login** — Enable Google/GitHub for developer tools, Google/Facebook for consumer apps (Apple has no built-in provider)
5. **Token lifetimes** — Keep access tokens short (minutes). A refresh token is bound to the user session: tune SSO Session Idle / SSO Session Max under Realm settings → Sessions instead of lengthening access tokens
6. **MFA for admins** — Require TOTP or WebAuthn for all admin and privileged accounts
7. **User federation** — Connect to existing LDAP/AD; Keycloak syncs users without migration
8. **Export realm config** — Export realm as JSON; store in Git for reproducible deployments. A `kc.sh export` contains users and credentials, so treat those files as secrets; the Admin Console's partial export leaves users out and masks secrets with `*`
9. **Never run `start-dev` in production** — it serves plain HTTP, uses an embedded file database and relaxes hostname checks
10. **Hide the admin surface** — Do not expose `/admin/`, `/realms/master/`, `/metrics` and `/health` to the internet; block them at the reverse proxy or put the console on a separate `--hostname-admin`
11. **Trust proxy headers only from your proxy** — with `--proxy-headers` set, a client that reaches Keycloak directly can forge `X-Forwarded-*`; the proxy must overwrite them and Keycloak must not be reachable around it
12. **Pin the image tag** — `quay.io/keycloak/keycloak:26.8.0`, not `latest`; read the upgrading guide before a major-version jump, as database migrations run on first start

Files in this skill

  • SKILL.md6.6 KB
  • _scores.json1.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.