> **Building with AI coding agents?** If you're using an AI coding agent, install the official Scalekit plugin. It gives your agent full awareness of the Scalekit API — reducing hallucinations and enabling faster, more accurate code generation.
>
> - **Claude Code**: `/plugin marketplace add scalekit-inc/claude-code-authstack` then `/plugin install <auth-type>@scalekit-auth-stack`
> - **GitHub Copilot CLI**: `copilot plugin marketplace add scalekit-inc/github-copilot-authstack` then `copilot plugin install <auth-type>@scalekit-auth-stack`
> - **Codex**: run the bash installer, restart, then open Plugin Directory and enable `<auth-type>`
> - **Skills CLI** (Windsurf, Cline, 40+ agents): `npx skills add scalekit-inc/skills --list` then `--skill <skill-name>`
>
> `<auth-type>` / `<skill-name>`: `agent-auth`, `full-stack-auth`, `mcp-auth`, `modular-sso`, `modular-scim` — [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Mobile & desktop applications

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.

:::tip
[**Check out the example apps on GitHub**](https://github.com/scalekit-inc/multiapp-demo) to see Web, SPA, Desktop, and Mobile apps sharing a single Scalekit session.
:::

## 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 native application registered in Scalekit with a `client_id` ([Create one](/authenticate/fsa/multiapp/manage-apps))
- 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`)

## High-level flow

```d2
shape: sequence_diagram

User
"Desktop / Mobile app"
"System browser"
Scalekit

User -> "Desktop / Mobile app": Click "Login"
"Desktop / Mobile app" -> "System browser": Open authorize URL
"System browser" -> Scalekit: Redirect to /oauth/authorize 
 (+ state + PKCE challenge)
Scalekit -> "System browser": Redirect to callback URI with code + state
"System browser" -> "Desktop / Mobile app": Return control via deep link / loopback
"Desktop / Mobile app" -> Scalekit: POST /oauth/token 
 (+ code_verifier)
Scalekit -> "Desktop / Mobile app": access_token, refresh_token, id_token
"Desktop / Mobile app" -> "Desktop / Mobile app": Store tokens + continue
```

## Step-by-step implementation

1. ## Initiate login or signup

   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.

   ```sh
   <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
**Why PKCE is required for native apps:** Native apps cannot keep a `client_secret` secure because the secret would be embedded in the application binary and could be extracted through reverse engineering. PKCE protects against authorization code interception attacks where malware on the device captures the authorization code from the callback URI.

   For detailed parameter definitions, see [Initiate signup/login](/authenticate/fsa/implement-login).

2. ## Handle the callback and complete login

   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`

   ```sh
   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>
   ```

   ```json
   {
     "access_token": "...",
     "refresh_token": "...",
     "id_token": "...",
     "expires_in": 299
   }
   ```
**Authorization codes expire after one use:** Authorization codes are single-use and expire quickly (approximately 10 minutes). If you attempt to reuse a code or it expires, start a new login flow to obtain a fresh authorization code.

3. ## Manage sessions and token refresh

   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](#token-storage-security) for platform-specific recommendations.

   When an access token expires, request new tokens:

   ```sh
   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:

   ```sh
   <ENV_URL>/keys
   ```

4. ## Implement logout

   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

   ```sh
   <ENV_URL>/oidc/logout?
     id_token_hint=<ID_TOKEN>&
     post_logout_redirect_uri=<POST_LOGOUT_REDIRECT_URI>
   ```
**Logout must open the system browser:** Use the system browser to navigate to the `/oidc/logout` endpoint, not a backend API call. The browser ensures Scalekit's session cookie is sent with the request, allowing Scalekit to identify and terminate the correct session.

## Handle errors

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:

| 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

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:

| Platform | Recommended Storage |
|----------|---------------------|
| iOS | Keychain Services |
| Android | EncryptedSharedPreferences or Keystore |
| macOS | Keychain |
| Windows | Windows Credential Manager or DPAPI |
| Linux | Secret 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
**Never embed secrets in your application binary:** Credentials embedded in application code or configuration files can be extracted through reverse engineering. Always use PKCE for native apps instead of relying on a `client_secret`. If you need to make authenticated API calls from your backend, use a separate web application with proper secret management.

## What's next

- [Set up a custom domain](/guides/custom-domain) for your authentication pages
- [Add enterprise SSO](/authenticate/auth-methods/enterprise-sso/) to support SAML and OIDC with your customers' identity providers

---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
