Skip to main content

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

ParameterRequiredDescription
client_idYesYour application's client ID
redirect_uriYesWhere to redirect after authentication (must be registered)
response_typeYesMust be code for this flow
scopeYesSpace-separated list of requested scopes (minimum: openid)
stateYesA random string to prevent CSRF attacks (store it, verify it on callback)
nonceNoA random string included in the ID token (replay protection)
promptNologin (force sign-in), none (silent), consent (force consent)
login_hintNoPre-fill the email field (e.g. user@example.com)
response_modeNoquery (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:

  1. Enter their email and password
  2. Use a Magic Link (passwordless)
  3. Sign in with an external provider (Google, Facebook, Seznam.cz)
  4. 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
Important

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:

ParameterRequiredDescription
grant_typeYesMust be authorization_code
client_idYesYour application's client ID
client_secretYesYour application's client secret
codeYesThe authorization code from the callback
redirect_uriYesMust 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:

FieldDescription
access_tokenA 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_typeAlways Bearer
expires_inToken lifetime in seconds (1800 = 30 minutes)
refresh_tokenAn opaque token used to obtain new access tokens (only if the offline_access scope was requested)
id_tokenA JWT containing claims about the user's identity
scopeThe scopes that were granted (may differ from those requested)
Access token vs. refresh token

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:

ErrorHTTP statusCauseResolution
invalid_request400A required parameter is missingCheck all required parameters
invalid_client401Invalid client_id or secretVerify your credentials
invalid_grant400The code expired, was already used, or is invalidRequest a new authorization code
invalid_scope400The requested scope is not allowedRequest only allowed scopes