Keycloak (OIDC/OAuth2)¶
Keycloak is an open-source identity provider for testing the auth module without external IdP accounts.
Features¶
OIDC/OAuth2 compliant: Full OpenID Connect implementation
Pre-configured realm: Test users and clients ready to use
PKCE support: Public client with S256 code challenge
Offline access: Refresh token testing
Quick Start¶
cd infra
# Start Keycloak only
docker compose up -d keycloak
# Wait for health check (about 30s on first start)
docker compose logs -f keycloak
Keycloak is ready when you see Running the server in development mode.
Pre-configured Resources¶
The realm (keycloak/realm-export.json) is auto-imported on startup:
Test Realm¶
Setting |
Value |
|---|---|
Realm |
|
Display Name |
KSTLib Test Realm |
SSL Required |
None (dev mode) |
Test User¶
Field |
Value |
|---|---|
Username |
|
Password |
|
|
|
Name |
Test User |
Clients¶
Public Client (PKCE)¶
For CLI/desktop apps using Authorization Code + PKCE flow:
Setting |
Value |
|---|---|
Client ID |
|
Client Secret |
None (public client) |
PKCE Method |
S256 |
Redirect URIs |
|
Scopes |
openid, profile, email, offline_access |
Confidential Client¶
For server-to-server or testing client_secret flows:
Setting |
Value |
|---|---|
Client ID |
|
Client Secret |
|
Grant Types |
Authorization Code, Client Credentials, Direct Access |
Redirect URIs |
|
Scopes |
openid, profile, email, offline_access |
Usage¶
kstlib Configuration¶
Add a test provider to your config:
# config.yml
auth:
providers:
keycloak-test:
type: oidc
discovery_url: http://localhost:8080/realms/kstlib-test/.well-known/openid-configuration
client_id: kstlib-test-public
# No client_secret for public clients
scopes:
- openid
- profile
- email
- offline_access
callback_server:
port: 8400
CLI Login¶
# Start Keycloak if not running
cd infra && docker compose up -d keycloak
# Login with kstlib CLI
kstlib auth login keycloak-test
# Check status
kstlib auth status keycloak-test
# Get user info
kstlib auth whoami keycloak-test
Python Integration¶
from kstlib.auth import OIDCProvider
provider = OIDCProvider(
name="keycloak-test",
discovery_url="http://localhost:8080/realms/kstlib-test/.well-known/openid-configuration",
client_id="kstlib-test-public",
scopes=["openid", "profile", "email"],
)
# Get authorization URL for browser
auth_url, state, code_verifier = provider.get_authorization_url_with_pkce()
# After callback with authorization code
token = provider.exchange_code(code, state, code_verifier)
# Get user info
userinfo = provider.get_userinfo()
print(f"Logged in as: {userinfo['name']}")
OIDC vs OAuth2¶
OIDC (OpenID Connect) is OAuth2 + identity layer. The key difference:
OIDC: Just provide
issuerURL, all endpoints auto-discovered via/.well-known/openid-configurationOAuth2: Must configure each endpoint manually (
authorize_url,token_url, etc.)
Tip
Test the discovery endpoint in your browser: http://localhost:8080/realms/kstlib-test/.well-known/openid-configuration
This JSON contains all endpoints, supported scopes, signing algorithms, etc.
Endpoints Reference¶
All endpoints are under the realm URL http://localhost:8080/realms/kstlib-test:
Endpoint |
Path |
Description |
|---|---|---|
Discovery |
|
OIDC auto-discovery (all config in JSON) |
Authorization |
|
User login page |
Token |
|
Exchange code for tokens |
UserInfo |
|
Get user profile (requires access_token) |
End Session |
|
Logout endpoint |
JWKS |
|
Public keys for JWT verification |
Admin Console¶
Access the Keycloak admin console:
Username:
adminPassword:
admin
From here you can:
Create additional users
Configure new clients
Modify realm settings
View active sessions
Configuration¶
Environment variables in .env:
Variable |
Default |
Description |
|---|---|---|
|
8080 |
HTTP port |
CI/CD Integration¶
GitHub Actions¶
services:
keycloak:
image: quay.io/keycloak/keycloak:24.0
ports:
- 8080:8080
env:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
options: >-
--health-cmd "exec 3<>/dev/tcp/localhost/8080"
--health-interval 10s
--health-timeout 5s
--health-retries 10
steps:
- name: Wait for Keycloak
run: |
timeout 60 bash -c 'until curl -sf http://localhost:8080/health/ready; do sleep 2; done'
pytest Fixture¶
import pytest
from kstlib.auth import OIDCProvider
@pytest.fixture
def keycloak_provider():
"""OIDC provider configured for local Keycloak."""
return OIDCProvider(
name="keycloak-test",
discovery_url="http://localhost:8080/realms/kstlib-test/.well-known/openid-configuration",
client_id="kstlib-test-public",
scopes=["openid", "profile", "email"],
)
@pytest.fixture
def keycloak_token(keycloak_provider):
"""Get a token using direct access grant (test only)."""
# Note: Direct access only works with confidential client
import httpx
response = httpx.post(
"http://localhost:8080/realms/kstlib-test/protocol/openid-connect/token",
data={
"grant_type": "password",
"client_id": "kstlib-test-confidential",
"client_secret": "kstlib-test-secret",
"username": "testuser",
"password": "testpass123",
"scope": "openid profile email",
},
)
response.raise_for_status()
return response.json()["access_token"]
Troubleshooting¶
Container won’t start¶
# Check logs
docker compose logs keycloak
# Verify port is available
netstat -an | findstr 8080 # Windows
lsof -i :8080 # Linux/Mac
Realm not imported¶
The realm is imported from keycloak/realm-export.json on first start. If the realm is missing:
# Full reset (deletes all data)
docker compose down -v
docker compose up -d keycloak
Token exchange fails¶
Verify redirect URI matches exactly (including trailing slashes)
Check PKCE code_verifier is being sent for public clients
Ensure client_secret is provided for confidential clients
CORS errors¶
The test realm is configured for http://127.0.0.1:8400 and http://localhost:8400. If using a different port, update the client’s Web Origins in the admin console.
See Also¶
Authentication - Auth module documentation
Quickstart - Getting started with auth
Providers - Provider configuration