Authentication (OIDC/JWT)¶
TrustDeck is protected using OpenID Connect (OIDC) with JWT access tokens issued by Keycloak.
Clients call the REST API by sending an access token as a Bearer token.
1) What you need to call the API¶
You need:
- A running Keycloak (dev or prod)
- A Keycloak realm for TrustDeck
- A Keycloak client for TrustDeck (confidential/public depending on your flow)
- An access token (JWT) issued for that realm/client
Requests must include:
Authorization: Bearer <ACCESS_TOKEN>
2) Configure TrustDeck to validate tokens (issuer / realm)¶
TrustDeck validates JWTs against your Keycloak realm.
In your trustdeck.env, ensure these are set consistently (names may differ depending on your env template, but the idea is the same):
KEYCLOAK_SERVER_URI(base URL of Keycloak, no trailing slash)KEYCLOAK_REALM_NAME(realm name)KEYCLOAK_CLIENT_ID(client id that your tokens are issued for / that contains roles)KEYCLOAK_CLIENT_SECRET(if you use confidential clients, e.g., client-credentials)KEYCLOAK_HOSTNAME/KEYCLOAK_RELATIVE_PATH(if your deployment uses a path or reverse proxy)
Example:
KEYCLOAK_SERVER_URI=http://localhost:8081
KEYCLOAK_REALM_NAME=trustdeck
KEYCLOAK_CLIENT_ID=trustdeck
KEYCLOAK_CLIENT_SECRET=change-me
Note
- If Keycloak is behind a proxy, make sure the externally visible URL matches what Keycloak puts into the token’s
issclaim. - If issuer URLs don’t match, you’ll typically see
401 Unauthorized(invalid token / invalid issuer).
3) How authorization usually works¶
Our setup authorizes requests by checking roles carried in the JWT or group membership.
Accordingly, a client roles mapper is needed. Add it to your client, if it's missing.
First, roles are checked on some endpoints. For that, a lookup in the JWT is sufficient.
Since we do dynamically add new roles during runtime when e.g. creating a new project or domain,
simple roles would soon create huge JWTs, as we would need to add about 40 name-dependant roles per domain or project
(e.g. project-entity-create-ExampleProject). To avoid this, we model these roles as groups.
project-entity-create-ExampleProject is modelled as the group (with subgroups) /Project/project-entity-create/ExampleProject
with the user being a member of this group. Since now, everytime a request to an endpoint requires to check such a membership means
that we have to ask the Keycloak instance is user ABC in group XYZ.
To reduce the number of times these checks against Keycloak need to be made, we added caching for this functionality.
When creating or deleting projects and domains, TrustDeck automatically adds or deletes group memberships and roles to Keycloak and the local cache.
Note
- On startup, TrustDeck checks the roles mentioned in the
src/main/resources/application.yml-file. Roles that are missing in Keycloak will then be created as well as the respective groups. - In JWTs issued by Keycloak, client roles typically appear under:
resource_access.<client-id>.roles
What you should do:
- Assign the required client roles and group memberships to the user (or service account) in Keycloak
- Verify the access token contains them before calling TrustDeck
4) Getting an access token¶
4.1 Service-to-service (Client Credentials)¶
Use this if the TrustDeck client is confidential and has a client secret (good for technical clients).
1) Ensure these Keycloak client settings:
- Access Type: Confidential (Client authentication turned on)
- Service Accounts Enabled: On
- Client has appropriate client roles assigned to its service account (or via role mappings)
2) Example request to acquire a token:
KEYCLOAK_BASE=http://localhost:8081
REALM=trustdeck
CLIENT_ID=trustdeck
CLIENT_SECRET=change-me
curl -s \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
"${KEYCLOAK_BASE}/realms/${REALM}/protocol/openid-connect/token"
3) Extract the access token (example using jq):
curl -s \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
"${KEYCLOAK_BASE}/realms/${REALM}/protocol/openid-connect/token" \
| jq -r .access_token
5) Calling TrustDeck with the token¶
Once you have an access token:
BASE_URL=http://localhost:8080
TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -i \
-H "Authorization: Bearer ${TOKEN}" \
-H "Accept: application/json" \
"${BASE_URL}/api/projects"
If the token is accepted, you should get a successful response.
As the endpoint requires a specific role, you may still get 403 Forbidden if the role is missing.
6) Troubleshooting¶
6.1 401 Unauthorized¶
Common causes:
- Missing
Authorizationheader - Token expired
- Token issuer (
iss) does not match what TrustDeck expects - TrustDeck cannot reach Keycloak JWKS endpoint (network / TLS / proxy)
What to check:
- Does the request include
Authorization: Bearer ...? - Does the token’s
issmatch your Keycloak realm URL? - Can TrustDeck reach:
<KEYCLOAK_BASE>/realms/<REALM>/protocol/openid-connect/certs(This is where Keycloak publishes public keys for JWT verification.)
6.2 403 Forbidden¶
Common causes:
- Token is valid, but the user/client lacks required roles
What to check:
- The token contains client roles under:
resource_access.<client-id>.roles - The user/service account has the necessary roles and group memberships assigned in Keycloak
6.3 “Roles don’t appear in the token”¶
Common causes:
- Client roles not assigned
- Token mapper not configured to include required claims
Fix:
- Assign client roles on the TrustDeck client
- Ensure the access token includes client roles (Keycloak defaults usually do)
7) Notes for dev vs prod¶
Dev mode¶
- Keycloak commonly runs on:
http://localhost:8081 - TrustDeck backend commonly runs on:
http://localhost:8080
Prod-like mode (Docker)¶
- Same URLs as above unless you place Keycloak behind a reverse proxy.
- If using TLS and private CAs, ensure TrustDeck trusts the CA (truststore settings).