- Replace gorilla/csrf with net/http CrossOriginProtection - Require valkey-password and add TLS options for session store - End session at /logout and revoke refresh tokens - Re-derive identity and roles from provider every five minutes - Process each Stripe webhook event in its own Temporal workflow - Give each outbox entry its own workflow with Temporal retries - Guard against stale Stripe events with provider timestamps - Derive transport security from base-url scheme
7.1 KiB
title, audience, summary
| title | audience | summary | ||
|---|---|---|---|---|
| Identity Provider Setup |
|
Configure an OIDC identity provider (Keycloak-focused) so the console can authenticate users and assign the operator role. |
Identity Provider Setup
This guide covers configuring your OIDC identity provider to work with the member console. The examples use Keycloak but the concepts apply to any OIDC-compliant provider.
Prerequisites
- An OIDC identity provider with a client configured for Authorization Code + PKCE flow
- The client ID and secret configured in
mc-config.yaml(or equivalent flags) - Refresh tokens issued to the client (Keycloak's default). Two things use them: every five minutes a signed-in session re-derives its identity and roles from a fresh ID token, so a role removed, an account disabled, or a session ended at the provider takes effect here within that bound; and sign-out mints a fresh ID token for a valid
id_token_hint, so the provider ends its session without asking. Without a refresh token a session keeps its sign-in roles for its lifetime and the provider prompts at sign-out. The roles claim must be present in the ID token a refresh returns, not only the one sign-in returns (Keycloak's mappers do both). end_session_endpointpublished in the provider's discovery document (OpenID Connect RP-Initiated Logout 1.0). The console refuses to start without it.revocation_endpointpublished in the discovery document (RFC 7009), so the console can revoke the refresh token at sign-out. Optional; without it the console warns once at startup and skips revocation.
Standard OIDC Claims
The member console reads the following standard claims from the ID token:
| Claim | Used for |
|---|---|
sub |
Unique user identifier (OIDC subject) |
email |
User's primary email |
email_verified |
Whether the email is verified |
name |
Display name |
preferred_username |
Username (used for org slug derivation) |
On first login, the member console auto-provisions a person, organization, and workspace from these claims.
Role-Based Access: Operator Role
The member console uses a single application-level role to gate access to the operator panel:
- Role name:
operator-member - Where it's checked:
internal/server/operator.goandoperator_partials.go
How roles are extracted
The member console extracts roles from the ID token, the token OIDC defines for the client to consume, after it has passed signature and nonce verification. The access token is not read: OAuth2 leaves its format opaque, so parsing it would only work with providers that happen to issue JWT access tokens. Within the ID token the console reads every claim location an identity provider might use to carry roles, rather than trusting only one:
roles(top-level array)groups(top-level array)realm_access.roles(Keycloak realm roles)resource_access.<client-id>.roles(Keycloak client-scoped roles, read only for this app's configured client ID; roles belonging to other clients are never included)
The member console reads all four locations and merges their roles into one deduplicated list: a role that appears in more than one location is kept once, at the position where it first appears (the locations are read in the order listed above, and within a location, in the order the claim lists them). Because every location is read, a deployment can put operator-member in whichever claim its identity provider produces and the member console still recognizes it.
If the ID token carries none of the four locations at all, the identity provider is not mapping roles into the ID token. The console logs a warning naming the signed-in email and this document on every such login; without it, the only symptom would be an operator who cannot get into the operator panel. A location that is present but empty is an ordinary member with no roles and is not logged.
Keycloak setup
There are two ways to make the operator-member role visible in the ID token:
Option A: Client role with a protocol mapper (recommended)
- Go to Clients > member-console > Roles
- Create a role named
operator-member - Assign it to the desired user(s) under Users > (user) > Role Mappings > member-console
- Ensure there is a protocol mapper that emits client roles into a top-level
rolesclaim in the ID token:- Mapper type: User Client Role
- Client ID:
member-console - Token Claim Name:
roles - Claim JSON Type: String
- Multivalued: On
- Add to ID token: On (Add to access token is not required)
This is the approach used by the test stack's Keycloak seed (test/seed/keycloak/seed-keycloak.sh).
Option B: Realm role
- Go to Realm Roles
- Create a role named
operator-member - Assign it to the desired user(s)
- Enable Add to ID token on the realm roles mapper (Client scopes > roles > Mappers > realm roles); it is off by default, so without this step the role reaches only the access token, which the console does not read. The role then appears in
realm_access.rolesin the ID token.
This is simpler but mixes application roles with realm-level roles.
Other identity providers
For providers other than Keycloak, ensure that the ID token includes operator-member in one of the four checked claim locations (roles, groups, realm_access.roles, or resource_access.<client-id>.roles). Most providers support custom claims or group mappings that can achieve this.
Temporal Authorization
Temporal requires its own role setup for JWT-based authorization. See Temporal Authorization Setup for the full Temporal permissions configuration.
Test Environment
The test stack's Keycloak seed (test/seed/keycloak/seed-keycloak.sh) pre-configures:
- A
member-consoleclient with theoperator-memberclient role defined - The admin user mapped to the
operator-memberrole - Protocol mappers that emit roles into the ID token (and the access token)
To use the test environment:
cd test
docker compose up -d
member-console start
Login with admin / admin at http://localhost:8081 — this is Keycloak's
own admin console, a separate system from member-console itself. The
console's operator login (alice / password) is covered in the
README quickstart.
Run app OIDC on a dedicated realm, not Keycloak's
masteradmin realm. The member-facing "Identity and Access" link opens Keycloak's account console; on themasteradmin realm that console errors ("Something went wrong") because the minted token carries noaud: accountclaim. The test stack therefore creates and runs on a dedicatedtestrealm, where the account console works out of the box — do the same in production. Seestatus/issues.md.
Note: The first
member-console startafter a freshdocker compose up -dused to fail with a Temporal auth error. This is fixed — compose now gates Temporal on its database and Keycloak being healthy, and member-console retries the initial Temporal connection. Seestatus/issues.md.