Skip to content
Talk to an Engineer Dashboard

Single page application

Implement Multi-App Authentication for single page apps using Authorization Code with PKCE

Implement login, token management, and logout in your single page application (SPA) using Authorization Code with PKCE. SPAs run entirely in the browser and cannot securely store a client_secret, so they use PKCE (Proof Key for Code Exchange) to protect the authorization flow. This guide covers initiating login from your SPA, exchanging authorization codes for tokens, managing sessions, and implementing logout.

Before you begin, ensure you have:

  • A Scalekit account with an environment configured
  • Your environment URL (ENV_URL), e.g., https://yourenv.scalekit.com
  • A SPA registered in Scalekit with a client_id (Create one)
  • At least one redirect URL configured in Dashboard > Developers > Applications > [Your App] > Redirects
UserSingle page app (browser)Scalekit Click "Login" Redirect to /oauth/authorize (+ state + PKCE challenge) Redirect to /callback with code + state POST /oauth/token access_token, refresh_token, id_token Store tokens + continue
  1. Initiate login by redirecting the user to Scalekit’s hosted login page. Include the PKCE code challenge in the authorization request to protect against authorization code interception attacks.

    Terminal window
    <ENV_URL>/oauth/authorize?
    response_type=code&
    client_id=<CLIENT_ID>&
    redirect_uri=<CALLBACK_URL>&
    scope=openid+profile+email+offline_access&
    state=<RANDOM_STATE>&
    code_challenge=<PKCE_CODE_CHALLENGE>&
    code_challenge_method=S256

    Generate and store these values before redirecting:

    • state — Validate this on callback to prevent CSRF attacks
    • code_verifier — A cryptographically random string you keep locally
    • code_challenge — Derived from the verifier using S256 hashing; send this in the authorization URL

    For detailed parameter definitions, see Initiate signup/login.

  2. After authentication, Scalekit redirects the user back to your callback URL with an authorization code and the state you sent.

    Your callback handler must:

    • Validate the returned state matches what you stored — this confirms the response is for your original request
    • Handle any error parameters before processing
    • Exchange the authorization code for tokens by including the code_verifier
    Terminal window
    POST <ENV_URL>/oauth/token
    Content-Type: application/x-www-form-urlencoded
    grant_type=authorization_code&
    client_id=<CLIENT_ID>&
    code=<CODE>&
    redirect_uri=<CALLBACK_URL>&
    code_verifier=<PKCE_CODE_VERIFIER>
    {
    "access_token": "...",
    "refresh_token": "...",
    "id_token": "...",
    "expires_in": 299
    }
  3. Store tokens and validate them on each request. When access tokens expire, use the refresh token to obtain new ones without requiring the user to authenticate again.

    Token roles

    • Access token — Short-lived token (default 5 minutes) for authenticated API requests
    • Refresh token — Long-lived token to obtain new access tokens
    • ID token — JWT containing user identity claims; required for logout

    Store tokens client-side based on your security requirements. See Token storage security for guidance on choosing the right storage mechanism.

    When an access token expires, request new tokens:

    Terminal window
    POST <ENV_URL>/oauth/token
    Content-Type: application/x-www-form-urlencoded
    grant_type=refresh_token&
    client_id=<CLIENT_ID>&
    refresh_token=<REFRESH_TOKEN>

    Validate access tokens by verifying:

    • Token signature using Scalekit’s public keys (JWKS endpoint)
    • iss matches your Scalekit environment URL
    • aud includes your client_id
    • exp and iat are valid timestamps

    Public keys for signature verification:

    Terminal window
    <ENV_URL>/keys
  4. Clear your local session and redirect to Scalekit’s logout endpoint to invalidate the shared session.

    Your logout action must:

    • Extract the ID token before clearing local storage
    • Clear locally stored tokens from memory or storage
    • Redirect the browser to Scalekit’s logout endpoint
    Terminal window
    <ENV_URL>/oidc/logout?
    id_token_hint=<ID_TOKEN>&
    post_logout_redirect_uri=<POST_LOGOUT_REDIRECT_URI>

When authentication fails, Scalekit redirects to your callback URL with error parameters instead of an authorization code:

Terminal window
/callback?error=access_denied&error_description=User+denied+access&state=<STATE>

Check for errors before processing the authorization code:

  • Check if the error parameter exists in the URL
  • Log the error and error_description for debugging
  • Display a user-friendly message
  • Provide an option to retry login

Common error codes:

ErrorDescription
access_deniedUser denied the authorization request
invalid_requestMissing or invalid parameters (e.g., invalid PKCE challenge)
server_errorScalekit encountered an unexpected error

SPAs run entirely in the browser where tokens are vulnerable to cross-site scripting (XSS) attacks. An attacker who successfully injects malicious JavaScript can read tokens from any accessible storage and use them to impersonate the user.

Choose a storage strategy based on your security requirements:

StorageSecurityTrade-off
Memory (JavaScript variable)Most secure — not accessible to XSSTokens lost on page refresh; requires silent refresh
Session storageModerate — cleared when tab closesAccessible to XSS; persists during session
Local storageLeast secure — persists across sessionsAccessible to XSS; long exposure window

Recommendations:

  • For high-security applications, store tokens in memory and use silent refresh (iframe-based token renewal) to maintain sessions across page loads
  • Always sanitize user inputs and use Content Security Policy (CSP) headers to mitigate XSS attacks
  • Never log tokens or include them in error messages