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.
Prerequisites
Section titled “Prerequisites”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
High-level flow
Section titled “High-level flow”Step-by-step implementation
Section titled “Step-by-step implementation”-
Initiate login or signup
Section titled “Initiate login or signup”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=S256Your SPA should generate and store:
state(so you can validate the callback)code_verifier(kept locally), and the derivedcode_challenge(sent on/oauth/authorize)
For detailed definition of the base parameters, refer here
-
Handle the callback and complete login
Section titled “Handle the callback and complete login”After authentication, Scalekit redirects the user back to your callback URL with
codeandstate.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/tokenContent-Type: application/x-www-form-urlencodedgrant_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.
- Validate the returned
-
Manage sessions and token refresh
Section titled “Manage sessions and token refresh”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/tokenContent-Type: application/x-www-form-urlencodedgrant_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).
issmatches your Scalekit environmentaudmatches yourclient_idexpandiatare valid
Public keys for verification are available at:
Terminal window <ENV_URL>/keys -
Implement logout
Section titled “Implement logout”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.
Handle errors
Section titled “Handle errors”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:
- Check if the
errorparameter exists in the URL - Log the
erroranderror_descriptionfor debugging - Display a user-friendly message
- Provide an option to retry login
Common error codes:
| Error | Description |
|---|---|
access_denied | User denied the authorization request |
invalid_request | Missing or invalid parameters (e.g., invalid PKCE challenge) |
server_error | Scalekit encountered an unexpected error |
Token storage security
Section titled “Token storage security”SPAs run entirely in the browser, making token storage a security consideration. Choose a storage strategy based on your security requirements:
| Storage | Security | Trade-off |
|---|---|---|
| Memory (JavaScript variable) | Most secure | Tokens lost on page refresh |
| Session storage | Moderate | Cleared when tab closes; vulnerable to XSS |
| Local storage | Least secure | Persists 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
What’s next
Section titled “What’s next”- Set up a custom domain for your authentication pages
- Add enterprise SSO to support SAML and OIDC with your customers’ identity providers