Skip to content
Esc
navigateopen⌘Jpreview
Dashboard
On this page

Reuse website login for desktop and mobile apps

Implement web authentication for desktop and mobile apps using OAuth 2.0.

Overview

This pattern is useful if you want to have the same web authentication experience for your desktop and mobile apps. The implementation allows you to save development time but keep in mind that it does not involve a native authentication interface. Users get directed to a separate browser page where they complete the authentication flow and then return to your application.

The authentication flow works in the following way:

User accesses the native application

User completes the login attempt

  • The Authorization Service redirects the user to the registered callback URL.
    • Prefer an OS-claimed HTTPS universal link or app link. If you must use a custom scheme, follow the platform guidance for preventing other apps from claiming it.

The application completes the authorization code flow

  • The application verifies state, then exchanges the code with the original PKCE verifier.
  • It stores returned tokens only in platform-protected secure storage.
Reuse website login for desktop and mobile apps

Before you start

Enable paid features

This feature is only available to paid users. Follow the instructions below to enable it.

This feature is available only with the SuperTokens Managed Service.

Managed Service

  1. Sign in to the SuperTokens dashboard.
  2. Select the managed service option from the service type select component.
  3. Select your core instance from the next elemenet or create a new one.
  4. Open Features sub-page and enable the required ones.

Steps

1. Enable the Unified Login feature

Go to the SuperTokens.com SaaS Dashboard, select the relevant Managed deployment, and open Features. Enable Unified Login. Changes are saved automatically.

2. Create the OAuth2 Clients

For each native application, create a separate OAuth2 client. Call the SuperTokens Core API from a trusted administrative environment. Native applications are public clients: tokenEndpointAuthMethod must be none, and the application must never contain or receive a client secret. Use authorization code with S256 PKCE.

curl --location --request POST '<CORE_API_ENDPOINT>/recipe/oauth/clients' \
     --header 'api-key: <YOUR_API_KEY>' \
     --header 'Content-Type: application/json; charset=utf-8' \
     --data '
    {
      "clientName": "<YOUR_CLIENT_NAME>",
      "responseTypes": ["code"],
      "grantTypes": ["authorization_code", "refresh_token"],
      "tokenEndpointAuthMethod": "none",
      "scope": "offline_access <custom_scope_1> <custom_scope_2>",
      "redirectUris": ["https://app.example.com/oauth/callback"]
    }
'
const BASE_URL = "<CORE_API_ENDPOINT>";
const API_KEY = "<YOUR_API_KEY>";

const url = `${BASE_URL}/recipe/oauth/clients`;
const options = {
  method: "POST",
  headers: {
    "api-key": API_KEY,
    "Content-Type": "application/json; charset=utf-8",
  },
  body: JSON.stringify({
    clientName: "<YOUR_CLIENT_NAME>",
    responseTypes: ["code"],
    grantTypes: ["authorization_code", "refresh_token"],
    tokenEndpointAuthMethod: "none",
    scope: "offline_access <custom_scope_1> <custom_scope_2>",
    redirectUris: ["https://app.example.com/oauth/callback"],
  }),
};

fetch(url, options)
  .then((response) => response.json())
  .then((json) => console.log(json))
  .catch((err) => console.error(err));

import (
  "fmt"
  "net/http"
  "strings"
  "io"
)

func main() {
  baseUrl := "<CORE_API_ENDPOINT>"
  apiKey := "<YOUR_API_KEY>"
  url := fmt.Sprintf("%s/recipe/oauth/clients", baseUrl)
  payload := `{
    "clientName": "<YOUR_CLIENT_NAME>",
    "responseTypes": ["code"],
    "grantTypes": ["authorization_code", "refresh_token"],
    "tokenEndpointAuthMethod": "none",
    "scope": "offline_access <custom_scope_1> <custom_scope_2>",
    "redirectUris": ["https://app.example.com/oauth/callback"]
  }`

  req, _ := http.NewRequest("POST", url, strings.NewReader(payload))

  req.Header.Add("accept", "application/json")
  req.Header.Add("api-key", apiKey)
  req.Header.Add("content-type", "application/json")

  res, _ := http.DefaultClient.Do(req)

  defer res.Body.Close()
  body, _ := io.ReadAll(res.Body)

  fmt.Println(string(body))
}
import requests
from typing import Dict, Any

BASE_URL = "<CORE_API_ENDPOINT>"
API_KEY = "<YOUR_API_KEY>"

url = f"{BASE_URL}/recipe/oauth/clients"

payload: Dict[str, Any] ={
  "clientName": "<YOUR_CLIENT_NAME>",
  "responseTypes": ["code"],
  "grantTypes": ["authorization_code", "refresh_token"],
  "tokenEndpointAuthMethod": "none",
  "scope": "offline_access <custom_scope_1> <custom_scope_2>",
  "redirectUris": ["https://app.example.com/oauth/callback"]
}

headers = {
    "api-key": API_KEY,
    "Content-Type": "application/json",
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())

Creates an OAuth2 client Authorization: Set the api-key header to the value of your SuperTokens Core API key.

Request

Body Schema

Name Type Description Required Default Value
clientName string A human-readable name of the client used for identification. Yes -
grantTypes array of GrantType The grant types that the Client uses. Yes -
redirectUris array of string Exact redirect URIs registered for the client. Wildcards are not supported. Yes -
scope string String containing a space-separated list of scope values (as described in Section 3.3 of OAuth 2.0 [RFC6749]) that the client can use when requesting access tokens. Include the offline_access scope to exchange OAuth2 Refresh Tokens for OAuth2 Access Tokens No “”
responseTypes array of ResponseType The types of responses your client expects from the Authorization Server No -
tokenEndpointAuthMethod enum("client_secret_basic", "client_secret_post", "private_key_jwt", "none") The requested client authentication method No client_secret_basic
authorizationCodeGrantAccessTokenLifespan Time Duration OAuth2 Access Token lifespan when using the Authorization Code grant flow. No "1h"
authorizationCodeGrantIdTokenLifespan Time Duration OAuth2 ID Token lifespan when using the Authorization Code grant flow. No "1h"
authorizationCodeGrantRefreshTokenLifespan Time Duration OAuth2 Refresh Token lifespan when using the Authorization Code grant flow. If refreshTokenGrantRefreshTokenLifespan is also set "30d"
refreshTokenGrantRefreshTokenLifespan Time Duration OAuth2 Refresh Token lifespan when using the Refresh Token grant flow. Must match authorizationCodeGrantRefreshTokenLifespan. If authorizationCodeGrantRefreshTokenLifespan is also set "30d"
clientCredentialsGrantAccessTokenLifespan Time Duration OAuth2 Access Token lifespan when using the Client Credentials grant flow. No "1h"
enableRefreshTokenRotation boolean Indicates that the refresh token is a one-time use. Set it to false to disable refresh token rotation. No true

GrantType

  • authorization_code: allows exchanging the Authorization Code for an OAuth2 Access Token.
  • refresh_token: allows exchanging the OAuth2 Refresh Token for an OAuth2 Access Token.
  • client_credentials: allows the client to directly request an OAuth2 Access Token by authenticating itself with the Authorization Server using its own client credentials.

TokenEndpointAuthMethod

  • client_secret_basic: uses the HTTP Basic Authentication scheme to authenticate the client.
  • client_secret_post: uses the HTTP POST Authentication scheme to authenticate the client.
  • private_key_jwt: uses JSON Web Tokens (JWT) to authenticate the client.
  • none: indicates that the process of obtaining an OAuth2 Access Token does not use the client secret. Used for public clients (native apps or mobile apps).

ResponseType

  • code: Indicates that the Client receives an Authorization Code that it exchanges for an OAuth2 Access Token.
  • id_token: Indicates that the Client expects an ID Token.

Time Duration

A string value that signifies time duration in milliseconds, seconds, minutes, or hours: "2000ms", "60s", "30m", "1h".

Example

curl -X POST <CORE_API_ENDPOINT>/recipe/oauth/clients \
  -H "Content-Type: application/json" \
  -H "api-key: <YOUR_API_KEY>" \
  -d '{
      "clientName": "<YOUR_CLIENT_NAME>",
      "responseTypes": ["code"],
      "grantTypes": ["authorization_code", "refresh_token"],
      "tokenEndpointAuthMethod": "none",
      "scope": "offline_access <custom_scope_1> <custom_scope_2>",
      "redirectUris": ["https://app.example.com/oauth/callback"]
    }'

Response

200

The client has been successfully created.

Relevant response fields

The response includes the persisted client configuration, including the fields below.

Property Type Description
clientName string The name of the client.
clientId string Unique identifier for the client.
clientSecret string Client secret for a confidential client. Omitted for a public client. Treat it as a credential and keep it on a trusted backend.
redirectUris array of string The URLs used for redirection.
audience array of string Value used to identify for whom a token is issued. The created client can generate access token only for the specified audiences.
scope string A space-separated string of scopes that the client can request.
responseTypes array of string Registered response types.
grantTypes array of string Registered grant types.
tokenEndpointAuthMethod string Token endpoint authentication method.
enableRefreshTokenRotation boolean Whether refresh token rotation is enabled.

Example

{
  "clientName": "<YOUR_CLIENT_NAME>",
  "clientId": "<CLIENT_ID>",
  "tokenEndpointAuthMethod": "none",
  "redirectUris": ["https://app.example.com/oauth/callback"],
  "audience": ["<YOUR_APPLICATION_DOMAIN>"],
  "scope": "offline_access <custom_scope_1> <custom_scope_2>"
}

Based on the client creation process, you can infer two additional values that you need later on:

  • authorizeUrl corresponds to <YOUR_API_DOMAIN>/auth/oauth/auth
  • tokenFetchUrl corresponds to <YOUR_API_DOMAIN>/auth/oauth/token

3. Configure the Authorization Service

Check one of the previous guides that show you how to set up the Authorization Service and then return to this page. Choose the tutorial based on whether you use multiple backend services or not:

4. Update the login flow in your applications

In each of your individual applications, you need to set up logic for handling the OAuth 2.0 authentication flow. Use a maintained native OAuth 2.0/OIDC library that uses the system browser and authorization code with S256 PKCE. For every request, let the library generate a fresh PKCE verifier and high-entropy state; verify state before code exchange. If you request openid, also generate and validate nonce and validate the ID token’s signature, issuer, audience, expiry, and nonce. Never use an embedded web view.

Register an exact callback URI. Prefer an OS-claimed HTTPS universal link or app link; use a custom scheme only when the platform’s interception protections are configured. Store access and refresh tokens in Keychain, Android Keystore-backed storage, or the platform equivalent. Never put tokens in logs, URLs, plain-text preferences, or application bundles.

You can use the react-native-app-auth library. Follow the instructions to set up your application.

Use authorization code with S256 PKCE and validate state. You can identify the configuration parameters from the response in step 2.

  • issuer corresponds to the endpoint of the Authorization Service <YOUR_API_DOMAIN>/auth
  • clientId corresponds to clientId
  • redirectUrl corresponds to a value from redirectUris
  • scopes is the space-separated scope value split into a list

You also need to set the additionalParameters property with the following values:

  • max_age: 0 This forces a new authentication flow once the user ends up on the Authorization Service frontend.
  • tenant_id: <TENANT_ID> Optional, in case you are using a multi tenant setup. Set this to the actual tenant ID.

You can use the AppAuth-Android library. Follow the instructions to set up your application.

Use authorization code with S256 PKCE and validate state. You can identify the configuration parameters from the response in step 2.

For the AuthorizationServiceConfiguration, the parameters you need to provide are: authorizeUrl and tokenFetchUrl. When calling the AuthorizationRequest.Builder function you can use clientId and a value from redirectUris to replace the example values.

You need to set additional query parameters by calling the setAdditionalParameters function on the AuthorizationRequest.Builder object:

  • max_age: 0 This forces a new authentication flow once the user ends up on the Authorization Service frontend.
  • tenant_id: <TENANT_ID> Optional, in case you are using a multi tenant setup. Set this to the actual tenant ID.

You can use the AppAuth-iOS library. Follow the instructions to set up your application.

Use authorization code with S256 PKCE and validate state. You can identify the configuration parameters from the response in step 2.

  • clientId corresponds to clientId
  • redirectUrl corresponds to a value from redirectUris
  • scopes is the space-separated scope value split into a list
  • authorizationEndpoint corresponds to authorizeUrl
  • tokenEndpoint corresponds to tokenFetchUrl

You also need to set extra query parameters, when instantiating the OIDAuthorizationRequest object, with the following values:

  • max_age: 0 This forces a new authentication flow once the user ends up on the Authorization Service frontend.
  • tenant_id: <TENANT_ID> Optional, in case you are using a multi tenant setup. Set this to the actual tenant ID.

You can use the AppAuth library. Follow the instructions to set up your application.

Use authorization code with S256 PKCE and validate state. You can identify the configuration parameters from the response in step 2.

  • <client_id> corresponds to clientId
  • <issuer> corresponds to the endpoint of the Authorization Service <YOUR_API_DOMAIN>/auth
  • <redirect_url> corresponds to a value from redirectUris
  • scopes is the space-separated scope value split into a list

You also need to set the additionalParameters property with the following values:

  • max_age: 0 This forces a new authentication flow once the user ends up on the Authorization Service frontend.
  • tenant_id: <TENANT_ID> Optional, in case you are using a multi tenant setup. Set this to the actual tenant ID.

5. Test the new authentication flow

With everything set up, you can test your login flow. Use the setup created in the previous step to check if the authentication flow completes without any issues.

API reference

API schema and response details