Skip to content
Talk to an Engineer Dashboard

Single page application

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

A single page application (SPA) is a frontend-only application. It is a public OAuth client, so it does not use a client_secret. The SPA initiates login, handles the callback, and performs the authorization code exchange directly from the browser using Authorization Code with PKCE.

This guide covers the authorization code flow with PKCE for single page applications.

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. Login begins when your SPA redirects the user to Scalekit’s hosted login page using the authorization endpoint.

    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

    Your SPA should generate and store:

    • state (so you can validate the callback)
    • code_verifier (kept locally), and the derived code_challenge (sent on /oauth/authorize)

    For detailed definition of the base parameters, refer here

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

    Your SPA must:

    • Validate the returned state
    • Handle any error parameters
    • Exchange the authorization code for tokens (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
    }

    Note: Authorization codes are single-use and short-lived.

  3. Once tokens are issued, your SPA is responsible for session management.

    Token roles

    • Access token: short-lived, used for authenticated requests
    • Refresh token: used to obtain new tokens
    • ID token: user identity claims and required for logout

    For SPAs, tokens are stored client-side (for example, in memory, session storage, or local storage) depending on your application needs.

    When an access token expires, request new tokens using the refresh token:

    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 checking:

    • Verify the token signature using Scalekit’s public keys (JWKS).
    • iss matches your Scalekit environment
    • aud matches your client_id
    • exp and iat are valid

    Public keys for verification are available at:

    Terminal window
    <ENV_URL>/keys
  4. Logging out requires clearing your application session and invalidating the Scalekit session.

    Your logout action should:

    • Extract the ID token (if available)
    • Clear locally stored tokens
    • 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>

    Logout must be performed via a browser redirect so that Scalekit can identify and terminate the session.

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

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

Your callback handler should check for errors before processing the authorization code:

  1. Check if the error parameter exists in the URL
  2. Log the error and error_description for debugging
  3. Display a user-friendly message
  4. 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, making token storage a security consideration. Choose a storage strategy based on your security requirements:

StorageSecurityTrade-off
Memory (JavaScript variable)Most secureTokens lost on page refresh
Session storageModerateCleared when tab closes; vulnerable to XSS
Local storageLeast securePersists across sessions; vulnerable to XSS

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