KeySuiteTrousseau

OIDC Integration Guide

Detailed walkthrough of integrating Trousseau authentication using the Authorization Code flow with PKCE.

Overview

Trousseau implements the OpenID Connect Authorization Code flow with PKCE — the most secure standard flow for web and mobile applications.

This guide walks you through the complete flow, from redirecting the user to Trousseau to handling tokens and refreshing sessions.

Flow diagram

Your App                         Trousseau (IdP)
   │                                  │
   │  1. Generate PKCE verifier       │
   │     + code_challenge              │
   │                                  │
   │  2. Redirect to /authorize       │
   │  ─────────────────────────────>  │
   │                                  │
   │                                  │  3. User authenticates
   │                                  │     (email + password + MFA)
   │                                  │
   │  4. Redirect to callback         │
   │     with ?code=xxx               │
   │  <─────────────────────────────  │
   │                                  │
   │  5. POST /token                  │
   │     code + code_verifier         │
   │  ─────────────────────────────>  │
   │                                  │
   │  6. Receive tokens               │
   │     (id_token, access_token,     │
   │      refresh_token)              │
   │  <─────────────────────────────  │
   │                                  │
   │  7. Validate id_token            │
   │  8. Create app session           │

Endpoints

All endpoints can be discovered via the OpenID Configuration URL:

GET https://auth.keysuite.app/application/o/{your-slug}/.well-known/openid-configuration
EndpointURL
Authorizationhttps://auth.keysuite.app/application/o/authorize/
Tokenhttps://auth.keysuite.app/application/o/token/
UserInfohttps://auth.keysuite.app/application/o/userinfo/
JWKShttps://auth.keysuite.app/application/o/{your-slug}/jwks/
End Sessionhttps://auth.keysuite.app/application/o/{your-slug}/end-session/

Step 1: Authorization request

Redirect the user to the authorization endpoint with these parameters:

GET https://auth.keysuite.app/application/o/authorize/
  ?client_id={your-client-id}
  &response_type=code
  &scope=openid email profile
  &redirect_uri=https://app.yourapp.com/auth/callback
  &state={random-state-value}
  &code_challenge={code-challenge}
  &code_challenge_method=S256
ParameterRequiredDescription
client_idYesYour OIDC client ID
response_typeYesAlways code
scopeYesSpace-separated scopes (openid is required)
redirect_uriYesMust match a registered redirect URI
stateRecommendedRandom string to prevent CSRF attacks
code_challengeYesPKCE challenge (SHA-256 of the code verifier, base64url-encoded)
code_challenge_methodYesAlways S256

PKCE generation

// Generate a random code verifier
const codeVerifier = crypto.randomUUID() + crypto.randomUUID();

// Create the code challenge (SHA-256 hash, base64url-encoded)
const encoder = new TextEncoder();
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(codeVerifier));
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
  .replace(/\+/g, "-")
  .replace(/\//g, "_")
  .replace(/=/g, "");

PKCE is mandatory. Trousseau rejects authorization requests without a valid code_challenge. This protects against authorization code interception attacks.

Step 2: User authentication

Trousseau displays its login page. The user authenticates with their email and password. If MFA is configured, they are prompted for their second factor.

First-time users (no password yet) are automatically directed to a password setup page. This is transparent to your application — you always receive the same callback.

Step 3: Authorization callback

After successful authentication, Trousseau redirects to your redirect_uri:

GET https://app.yourapp.com/auth/callback
  ?code=abc123
  &state={your-state-value}

Always verify that the state parameter matches the value you sent in Step 1.

Step 4: Token exchange

Exchange the authorization code for tokens:

POST https://auth.keysuite.app/application/o/token/
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=abc123
&redirect_uri=https://app.yourapp.com/auth/callback
&client_id={your-client-id}
&client_secret={your-client-secret}
&code_verifier={code-verifier-from-step-1}

Response:

{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 300,
  "refresh_token": "eyJhbGciOi...",
  "id_token": "eyJhbGciOi...",
  "scope": "openid email profile"
}

Step 5: Validate the ID token

The id_token is a JWT containing the user's claims. Before trusting it:

  1. Verify the signature using the JWKS endpoint
  2. Check iss matches your issuer URL
  3. Check aud matches your client ID
  4. Check exp is in the future
  5. Check nonce if you sent one (optional)

Most OIDC libraries handle this validation automatically.

Decoded ID token example

{
  "iss": "https://auth.keysuite.app/application/o/your-slug/",
  "sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "aud": "your-slug-oidc",
  "exp": 1712400000,
  "iat": 1712399700,
  "email": "jean.dupont@hotel.com",
  "email_verified": true,
  "name": "Jean Dupont",
  "given_name": "Jean",
  "family_name": "Dupont",
  "picture": "https://auth.keysuite.app/media/avatars/..."
}

Step 6: Refresh tokens

Access tokens expire after 5 minutes. Use the refresh token to obtain new ones without requiring the user to re-authenticate:

POST https://auth.keysuite.app/application/o/token/
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token={refresh-token}
&client_id={your-client-id}
&client_secret={your-client-secret}

Response: Same format as the token exchange response, with new tokens.

Refresh tokens are valid for 30 days. After expiry, the user must re-authenticate via the authorization flow.

Error handling

Authorization errors

If authentication fails or the user denies access, Trousseau redirects to your callback with an error:

GET https://app.yourapp.com/auth/callback
  ?error=access_denied
  &error_description=User denied access
  &state={your-state-value}

Common error codes:

ErrorMeaning
access_deniedUser is not in the access group for your application
invalid_scopeYou requested a scope that is not allowed for your app
server_errorInternal error — retry or contact support

Token errors

ErrorMeaning
invalid_grantAuthorization code expired or already used
invalid_clientWrong client ID or secret
invalid_requestMissing required parameter