Authentication
Keycloak JWT authentication for Control Plane customer and workspace-scoped API calls.
All customer API endpoints require a Bearer JWT issued by the platform Keycloak realm.
Request header
Authorization: Bearer <access_token>
Content-Type: application/jsonexport CP_URL="https://dev-platform.groundfloor.cloud"
export TOKEN="<keycloak_access_token>"
curl -s -H "Authorization: Bearer $TOKEN" \
"$CP_URL/v1/workspaces" | jq .Token claims
| Claim | Usage |
|---|---|
sub | Stable user ID — mapped to Portal users row |
email | Display and membership lookup |
iss | Must match platform realm issuer (KEYCLOAK_ISSUER) |
Control Plane resolves sub → internal user ID, then checks SpiceDB for the requested action on the target resource.
Permission model
Authorization is ReBAC, not role strings alone. Common actions:
read · write · delete · ddl · administer · manage_members · view_billing · deploy
A 403 means SpiceDB denied the action — not an invalid token.
Workspace-scoped endpoints
Routes under /v1/workspaces/{workspace_id}/… require:
- Valid JWT
read/write/delete/ddlon that workspace (per endpoint)
Public endpoints
Unauthenticated, rate-limited:
| Endpoint | Purpose |
|---|---|
GET /v1/public/tls-authorize | ACME TLS authorization |
GET /v1/public/route | Edge hostname → upstream routing |
GET /v1/public/workspaces/{id}/apps/by-slug | Shell bootstrap |
See Front Door and Shell bootstrap.
Local development
| Variable | Customer Portal | Control Plane API |
|---|---|---|
| Keycloak URL | NEXT_PUBLIC_KEYCLOAK_URL | KEYCLOAK_ISSUER |
| API URL | NEXT_PUBLIC_API_URL | http://localhost:8088 |
Obtain a token by signing in to the Customer Portal locally, or use Keycloak direct grant in dev.
Errors
| Status | Meaning |
|---|---|
| 401 | Missing, malformed, or expired JWT |
| 403 | Valid JWT but SpiceDB denied the action |