Skip to content
Talk to an Engineer Dashboard

Mobile & desktop applications

Implement Multi-App Authentication for mobile and desktop apps using Authorization Code with PKCE

Implement login, token management, and logout in your mobile or desktop application using Authorization Code with PKCE. Native apps are public OAuth clients that cannot securely store a client_secret in the application binary, so they use PKCE to protect the authorization flow. This guide covers initiating login through the system browser, handling deep link callbacks, managing tokens in secure storage, 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 native application registered in Scalekit with a client_id (Create one)
  • A callback URI configured:
    • Mobile: Custom URI scheme (e.g., myapp://callback) or universal/app links
    • Desktop: Custom URI scheme or loopback address (e.g., http://127.0.0.1:PORT/callback)
UserDesktop / Mobile appSystem browserScalekit Click "Login" Open authorize URL Redirect to /oauth/authorize Redirect to callback URI with code + state Return control via deep link / loopback POST /oauth/token access_token, refresh_token, id_token Store tokens + continue
  1. Initiate login by opening the system browser with the authorization URL. Always use the system browser rather than an embedded WebView — this lets users leverage existing sessions and provides a familiar, secure authentication experience.

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

    Generate and store these values before opening the browser:

    • state — Validate this on callback to prevent CSRF attacks
    • code_verifier — A cryptographically random string you keep in the app
    • 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 application using the registered callback mechanism.

    Common callback patterns:

    • Mobile apps — Custom URI schemes (e.g., myapp://callback) or universal links (iOS) / app links (Android)
    • Desktop apps — Custom URI schemes or a temporary HTTP server on localhost

    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_URI>&
    code_verifier=<PKCE_CODE_VERIFIER>
    {
    "access_token": "...",
    "refresh_token": "...",
    "id_token": "...",
    "expires_in": 299
    }
  3. Store tokens in platform-specific secure storage and validate them on each request. When access tokens expire, use the refresh token to obtain new ones without requiring the user to re-authenticate.

    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 using secure, OS-backed storage appropriate for each platform. See Token storage security for platform-specific recommendations.

    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 the system browser to Scalekit’s logout endpoint to invalidate the shared session.

    Your logout action must:

    • Extract the ID token before clearing local storage
    • Clear tokens from secure storage
    • Open the system 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 URI with error parameters instead of an authorization code:

myapp://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 callback URI
  • Log the error and error_description for debugging
  • Display a user-friendly message in your app
  • 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

Native apps have access to platform-specific secure storage mechanisms that encrypt tokens at rest and protect them from other applications. Unlike browser storage, these mechanisms provide strong protection against token theft from device compromise or malware.

Use platform-specific secure storage for each platform:

PlatformRecommended Storage
iOSKeychain Services
AndroidEncryptedSharedPreferences or Keystore
macOSKeychain
WindowsWindows Credential Manager or DPAPI
LinuxSecret Service API (libsecret)

Recommendations:

  • Never store tokens in plain text files, shared preferences, or unencrypted databases — these can be read by any application with storage access
  • Use biometric or device PIN protection for sensitive token access when available — this adds a second factor for token access
  • Clear tokens from secure storage on logout — this ensures a clean state for the next authentication