From qa-auth-flows
Authors and runs integration tests against Keycloak - uses Testcontainers Keycloak module to spin up an isolated server per test class, imports realm JSON for fixtures, exercises OIDC discovery / token endpoint / token introspection / admin REST API; tests password / authorization-code / client-credentials / token-exchange flows; covers UMA (User-Managed Access) permission tickets. Use when the user works with self-hosted Keycloak and needs unit / integration tests for realms, clients, users, or auth flows.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-auth-flows:keycloak-testsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [keycloak.org/docs/latest/server_admin/index.html][kc-admin]:
Per keycloak.org/docs/latest/server_admin/index.html:
"Keycloak is a single sign on solution for web apps and RESTful web services."
The two foundational concepts per kc-admin:
"A realm manages a set of users, credentials, roles, and groups. A user belongs to and logs into a realm. Realms are isolated from one another and can only manage and authenticate the users that they control."
"Clients are entities that can request Keycloak to authenticate a user. Most often, clients are applications and services that want to use Keycloak to secure themselves and provide a single sign-on solution."
For tests: spin up Keycloak via Testcontainers, import a realm fixture (JSON exported from a known-good config), then exercise the OIDC endpoints from your application.
from testcontainers.keycloak import KeycloakContainer
import requests
@pytest.fixture(scope="session")
def keycloak():
with KeycloakContainer("quay.io/keycloak/keycloak:25.0") as kc:
kc.start()
yield kc
For Java:
@Container
static KeycloakContainer keycloak = new KeycloakContainer("quay.io/keycloak/keycloak:25.0")
.withRealmImportFile("test-realm.json");
The Testcontainers Keycloak module ships at
testcontainers.com/modules/keycloak.
Author one canonical test-realm.json per test scope (or per class
if scopes differ). Realm JSON exports from Admin Console (Realm
Settings → Action → Export). Strip secrets from exports before
checking in:
# Trim sensitive fields before commit
jq 'del(.. | .secret? // .credential.value? // empty)' realm.json > test-realm.json
Mount the file via withRealmImportFile() (Java) or environment
variable KEYCLOAK_IMPORT (Python testcontainers).
Per RFC 6749 (cited in oauth-flow-test-author)
the token endpoint accepts grant_type plus flow-specific params.
Keycloak's token endpoint:
{server-url}/realms/{realm-name}/protocol/openid-connect/token
def test_password_grant(keycloak):
server_url = keycloak.get_url()
response = requests.post(
f"{server_url}/realms/test/protocol/openid-connect/token",
data={
"grant_type": "password",
"client_id": "test-client",
"client_secret": "test-secret",
"username": "alice",
"password": "alicepass",
},
)
assert response.status_code == 200
body = response.json()
assert "access_token" in body
assert body["token_type"] == "Bearer"
assert "expires_in" in body
Note: the password grant is deprecated per RFC 9700 (Best Current Practice for OAuth 2.0 Security) - use it only for legacy test scenarios; new code should use authorization-code + PKCE.
For service-to-service auth:
def test_client_credentials_grant(keycloak):
response = requests.post(
f"{keycloak.get_url()}/realms/test/protocol/openid-connect/token",
data={
"grant_type": "client_credentials",
"client_id": "service-account-client",
"client_secret": "secret",
},
)
assert response.status_code == 200
token = response.json()["access_token"]
# Then call the protected resource:
api_response = requests.get(
"http://my-api/protected",
headers={"Authorization": f"Bearer {token}"},
)
assert api_response.status_code == 200
For RP (Resource Provider) integration:
def test_token_introspection(keycloak, access_token):
response = requests.post(
f"{keycloak.get_url()}/realms/test/protocol/openid-connect/token/introspect",
auth=("test-client", "test-secret"),
data={"token": access_token},
)
assert response.status_code == 200
body = response.json()
assert body["active"] is True
assert "username" in body
assert "preferred_username" in body
Per kc-admin, Keycloak supports custom auth flows (e.g., "Creating a password-less browser login flow"). Custom flows are configured via the Admin Console + exported in realm JSON.
Test pattern: import a realm with the custom flow, then exercise the browser flow via Playwright or a mock OIDC client. The exact pattern depends on the custom flow - consult the per-flow docs on kc-admin.
Keycloak exposes its admin functionality via REST. Pattern: obtain an admin-realm token, then call the admin endpoints:
def test_create_user_via_admin_api(keycloak):
admin_token = get_admin_token(keycloak)
response = requests.post(
f"{keycloak.get_url()}/admin/realms/test/users",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"username": "newuser",
"enabled": True,
"credentials": [{"type": "password", "value": "newpass", "temporary": False}],
},
)
assert response.status_code == 201
services:
# No service container needed — Testcontainers manages Keycloak per test
steps:
- run: pytest tests/integration/auth/ -v
Testcontainers requires Docker on the runner; GitHub Actions ubuntu-latest has it pre-installed.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Share a dev Keycloak between tests | Test interference; flaky | Per-class Testcontainers (Step 1) |
| Use password grant in tests for new flows | Deprecated per RFC 9700; misleading test coverage | Authorization-code + PKCE for new flows (cross-ref oauth-flow-test-author) |
| Commit secrets in realm JSON | Secrets leak | Strip via jq pipeline (Step 2) |
| Hardcode Keycloak URL | Tests fail when port changes | Use keycloak.get_url() (Step 1) |
| Skip introspection in resource-server tests | Token-validation logic untested | Always cover introspection or local JWT validation (Step 5) |
25.0) in
Testcontainers, not latest.k6-load-testing.oauth-flow-test-author)oauth-flow-test-author)auth0-tests,
okta-tests - sister IdP toolsoauth-flow-test-author,
session-management-test-author - build-an-X authorsnpx claudepluginhub testland/qa --plugin qa-auth-flowsProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.