Authentication API
Authentication is handled by Keycloak. The backend validates JWT tokens for all API requests.
Token Endpoints (Keycloak)​
Get Access Token (Authorization Code)​
Used by the frontend with PKCE flow.
POST /realms/openprime/protocol/openid-connect/token
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=openprime-app
&code=<authorization_code>
&redirect_uri=http://localhost:3000/callback
&code_verifier=<pkce_verifier>
Get Access Token (Client Credentials)​
For service-to-service or CLI access:
POST /realms/openprime/protocol/openid-connect/token
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=openprime-cli
&client_secret=<client_secret>
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"not-before-policy": 0,
"session_state": "uuid",
"scope": "openid profile email"
}
Refresh Token​
POST /realms/openprime/protocol/openid-connect/token
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&client_id=openprime-app
&refresh_token=<refresh_token>
Introspect Token​
Validate token (server-side):
POST /realms/openprime/protocol/openid-connect/token/introspect
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>
token=<access_token>
Response:
{
"active": true,
"sub": "user-uuid",
"preferred_username": "john.doe",
"email": "john.doe@example.com",
"email_verified": true,
"exp": 1699999999,
"iat": 1699996399,
"client_id": "openprime-app"
}
Logout​
POST /realms/openprime/protocol/openid-connect/logout
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
client_id=openprime-app
&refresh_token=<refresh_token>
User Info​
Get Current User​
Fetch the authenticated user's profile.
GET /api/users/me
Authorization: Bearer <access_token>
Response:
{
"data": {
"id": "uuid",
"keycloakId": "keycloak-uuid",
"username": "john.doe",
"email": "john.doe@example.com",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
}
Update Current User​
PUT /api/users/me
Authorization: Bearer <access_token>
Content-Type: application/json
{
"preferences": {
"theme": "dark",
"defaultProvider": "aws"
}
}
Token Claims​
The access token contains these claims:
{
"exp": 1699999999,
"iat": 1699996399,
"jti": "unique-token-id",
"iss": "http://localhost:8080/realms/openprime",
"aud": "account",
"sub": "user-uuid",
"typ": "Bearer",
"azp": "openprime-app",
"session_state": "session-uuid",
"acr": "1",
"realm_access": {
"roles": ["user", "admin"]
},
"resource_access": {
"openprime-app": {
"roles": ["environment-admin"]
}
},
"scope": "openid profile email",
"sid": "session-id",
"email_verified": true,
"preferred_username": "john.doe",
"given_name": "John",
"family_name": "Doe",
"email": "john.doe@example.com"
}
Error Responses​
401 Unauthorized​
Missing or invalid token:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
}
403 Forbidden​
Valid token but insufficient permissions:
{
"error": {
"code": "FORBIDDEN",
"message": "Insufficient permissions"
}
}
Token Expired​
{
"error": {
"code": "TOKEN_EXPIRED",
"message": "Access token has expired"
}
}
CLI Authentication​
For command-line tools, use device authorization flow or client credentials:
Device Authorization​
# 1. Request device code
curl -X POST \
"http://localhost:8080/realms/openprime/protocol/openid-connect/auth/device" \
-d "client_id=openprime-cli"
# Response includes verification_uri and user_code
# 2. User visits URL and enters code
# 3. Poll for token
curl -X POST \
"http://localhost:8080/realms/openprime/protocol/openid-connect/token" \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-d "client_id=openprime-cli" \
-d "device_code=<device_code>"
Security Best Practices​
- Store tokens securely - Never in localStorage for sensitive apps
- Use short expiry - Access tokens expire in 5 minutes
- Validate on every request - Backend validates JWT signature
- Use HTTPS - Always in production
- Implement token refresh - Before expiry to avoid interruptions