Authorization Code Flow
The Authorization Code Flow is the most secure and recommended flow for server-side applications that can securely store a client secret.
Flow diagram
┌──────────┐ ┌──────────┐ ┌──────────┐
│ │ │ │ │ │
│ User │ │ Your │ │ Klubero │
│ │ │ App │ │ SSO │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ 1. Clicks "Sign in" │ │
│ ─────────────────────────────────────► │ │
│ │ │
│ 2. Redirect to /connect/authorize │ │
│ ◄───────────────────────────────────── │ │
│ │ │
│ 3. Redirect to SSO │ │
│ ───────────────────────────────────────────────────────────────────────────────► │
│ │ │
│ 4. User authenticates & consents │ │
│ ◄─────────────────────────────────────────────────────────────────────────────── │
│ │ │
│ 5. Redirect to callback with code │ │
│ ───────────────────────────────────────────────────────────────────────────────► │
│ │ │
│ │ 6. POST /connect/token (code + secret) │
│ │ ──────────────────────────────────────► │
│ │ │
│ │ 7. Return tokens │
│ │ ◄────────────────────────────────────── │
│ │ │
│ 8. User signed in │ │
│ ◄───────────────────────────────────── │ │
Step 1: Build the authorization URL
Build a URL with the following parameters and redirect the user:
Base URL: https://your-sso-domain.com/connect/authorize
| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Your application's client ID |
redirect_uri | Yes | Where to redirect after authentication (must be registered) |
response_type | Yes | Must be code for this flow |
scope | Yes | Space-separated list of requested scopes (minimum: openid) |
state | Yes | A random string to prevent CSRF attacks (store it, verify it on callback) |
nonce | No | A random string included in the ID token (replay protection) |
prompt | No | login (force sign-in), none (silent), consent (force consent) |
login_hint | No | Pre-fill the email field (e.g. user@example.com) |
response_mode | No | query (default), fragment, or form_post |
Example authorization URL:
https://your-sso-domain.com/connect/authorize?\
client_id=my-app&\
redirect_uri=https%3A%2F%2Fmyapp.com%2Fcallback&\
response_type=code&\
scope=openid%20profile%20email%20offline_access&\
state=abc123xyz&\
nonce=nonce789
Step 2: The user authenticates
The user is shown the Klubero SSO sign-in page, where they can:
- Enter their email and password
- Use a Magic Link (passwordless)
- Sign in with an external provider (Google, Facebook, Seznam.cz)
- Complete two-factor authentication (if enabled)
Step 3: Handle the consent screen
If the user is using your application for the first time (or explicit consent is required), they'll see a consent screen showing:
- Your application's name and logo
- The requested permissions (scopes)
- Allow / Deny buttons
For trusted first-party applications, consent can be configured as "implicit" (automatic).
Step 4: Handle the callback
After successful authentication, the user is redirected to your redirect_uri:
https://myapp.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=abc123xyz
Verify that the state parameter matches what you sent in Step 1, to protect against CSRF attacks.
Error response:
If the user denies consent or an error occurs:
https://myapp.com/callback?error=access_denied&error_description=User%20denied%20access&state=abc123xyz
Step 5: Exchange the code for tokens
Send a POST request to the token endpoint:
curl -X POST https://your-sso-domain.com/connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=my-app" \
-d "client_secret=my-secret" \
-d "code=SplxlOBeZQQYbYS6WxSbIA" \
-d "redirect_uri=https://myapp.com/callback"
Token request parameters:
| Parameter | Required | Description |
|---|---|---|
grant_type | Yes | Must be authorization_code |
client_id | Yes | Your application's client ID |
client_secret | Yes | Your application's client secret |
code | Yes | The authorization code from the callback |
redirect_uri | Yes | Must exactly match the original request |
Successful response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9...",
"token_type": "Bearer",
"expires_in": 1800,
"refresh_token": "R2FtY2tqZ0hkY3BXcTk4dFZ3bE5mM2xEMkNq...",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"scope": "openid profile email offline_access"
}
Response fields:
| Field | Description |
|---|---|
access_token | A signed JWT (typ: at+jwt) for API authentication. It is not encrypted – the client can read it and verify the signature via JWKS. Expires in 30 minutes. |
token_type | Always Bearer |
expires_in | Token lifetime in seconds (1800 = 30 minutes) |
refresh_token | An opaque token used to obtain new access tokens (only if the offline_access scope was requested) |
id_token | A JWT containing claims about the user's identity |
scope | The scopes that were granted (may differ from those requested) |
The access_token is a signed but unencrypted JWT (typ: at+jwt) – the client can decode it and verify its signature using the public keys from jwks_uri. The refresh_token, by contrast, is an opaque, encrypted string – do not attempt to decode it or parse its contents. Treat it as an opaque secret and only send it back to the token endpoint.
Step 6: Use the access token
Include the access token in the Authorization header of your API requests:
curl https://your-sso-domain.com/connect/userinfo \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
Error handling
Common token endpoint errors:
| Error | HTTP status | Cause | Resolution |
|---|---|---|---|
invalid_request | 400 | A required parameter is missing | Check all required parameters |
invalid_client | 401 | Invalid client_id or secret | Verify your credentials |
invalid_grant | 400 | The code expired, was already used, or is invalid | Request a new authorization code |
invalid_scope | 400 | The requested scope is not allowed | Request only allowed scopes |