Native (Capacitor) auth
Inside a Capacitor (or other hybrid-native) WebView, the browser cookie that
backs a Torii session is unreliable: the WebView and the system browser don’t
share a cookie jar, and OAuth has to leave the WebView to a real browser and
come back. Native mode makes the SDK cookie-less instead. You give
<ToriiProvider> a tokenCache (a small adapter over your app’s secure
storage), and the SDK keeps the session token there, replaying it in an
X-Torii-Session-Token header rather than relying on a cookie.
This mirrors the token-cache model you may know from other native auth SDKs.
It is opt-in: a normal web app should not set tokenCache and should keep
using first-party cookies.
What changes in native mode
Section titled “What changes in native mode”When tokenCache is set, the SDK:
- Stores the opaque session token in your
tokenCache(never a cookie) and sends it in theX-Torii-Session-Tokenheader on refresh and logout. - Marks those requests
?_is_native=1and sends them withcredentials: 'omit'. Native origins are allow-listed without credentials, so a cookie would be blocked anyway. - On cold start, reads the stored token, refreshes it into a short-lived access token, and loads the user, with no boot-probe cookie round-trip.
- Runs OAuth through the system browser with a deep-link return instead of a same-tab redirect (see OAuth below).
The access token (JWT) is still what authenticates your /_torii/** API calls;
tokenCache only holds the long-lived session token used to mint new access
tokens.
Custom WebView origins
Section titled “Custom WebView origins”Native API calls are allow-listed by their WebView Origin, non-credentialed
(no cookie). The default Capacitor/Ionic origins work out of the box:
capacitor://localhost (iOS), https://localhost and http://localhost
(Android), and ionic://localhost. If you override server.hostname or
iosScheme in capacitor.config (e.g. capacitor://app.example.com), any host
on the capacitor:// and ionic:// schemes is still accepted. A custom Android
https:// host is an ordinary web origin instead: add it to Allowed Origins
in the dashboard. The Origin is not a security boundary in native mode (tenant
scoping is by host, auth is by the session token), so this controls whether your
WebView can read the response, not who may call the API.
Set up token-cache sessions
Section titled “Set up token-cache sessions”-
Implement
tokenCacheover your app’s secure storage. All three methods may be sync or async, and should not throw.import { Preferences } from '@capacitor/preferences';import type { ToriiTokenCache } from '@torii-js/torii-react';const KEY = 'torii_session';const tokenCache: ToriiTokenCache = {getToken: async () => (await Preferences.get({ key: KEY })).value,saveToken: (token) => Preferences.set({ key: KEY, value: token }),clearToken: () => Preferences.remove({ key: KEY }),};For production, prefer a Keychain/Keystore-backed plugin over
Preferencesso the token is encrypted at rest.import type { ToriiTokenCache } from '@torii-js/torii-react';let token: string | null = null;const tokenCache: ToriiTokenCache = {getToken: () => token,saveToken: (t) => {token = t;},clearToken: () => {token = null;},};The session does not survive an app restart. Fine for a spike, never ship it.
-
Pass it to
<ToriiProvider>. That single prop switches the SDK into native mode.<ToriiProvider publishableKey="pk_live_…" tokenCache={tokenCache}><App /></ToriiProvider> -
Use the SDK as usual.
<SignIn>,<SignUp>,useAuth(),getToken()all work unchanged; the cookie-less transport is internal.
OAuth in native mode
Section titled “OAuth in native mode”OAuth can’t complete inside the WebView: the provider’s consent page must run in the real system browser, then hand control back to your app via a deep link. You wire two more props and the SDK orchestrates the rest (including mandatory PKCE).
-
Register a deep-link target for your app. Prefer a claimed
httpslink (Android App Link / iOS Universal Link): the OS verifies domain ownership, so only your app can receive the return and another app cannot hijack it. A custom scheme (com.example.app://oauth-callback) also works and is handy in development, but any app can register the same scheme, so the return is interceptable; the mandatory PKCE check is what makes an intercepted return useless to an attacker. Use a claimedhttpslink in production. -
Allow-list it. In the dashboard, open Project settings → Allowed native redirects and add the exact target. The authorize call is rejected with
403if the redirect isn’t listed (an empty list disables native OAuth entirely). -
Provide a
nativeOAuthconfig (the system-browserbrowserbridge plus its deep-linkredirect). The bridge opensurlin the system browser and resolves with the deep-link URL your app is reopened with (ornullif the user dismisses it).import { Browser } from '@capacitor/browser';import { App } from '@capacitor/app';const nativeOAuth = {// Prefer a claimed https link in production; a com.example.app:// custom// scheme also works (handy in dev, but interceptable; see the note above).redirect: 'https://app.example.com/oauth-callback',browser: {openOAuth: (url: string) =>new Promise<string | null>((resolve) => {App.addListener('appUrlOpen', async ({ url: returnUrl }) => {await Browser.close();resolve(returnUrl);}).then((handle) => {// Remove the listener once we've resolved, to avoid leaks across// repeated sign-in attempts. (Omitted here for brevity.)void handle;});void Browser.open({ url });}),},};<ToriiProvider publishableKey="pk_live_…" tokenCache={tokenCache} nativeOAuth={nativeOAuth}><App /></ToriiProvider>
The SDK builds the PKCE pair, opens the authorize URL through your bridge,
parses the returned deep link, and establishes the session. It surfaces any
provider error through the same oauthError channel the web flow uses, and
shows the legal-consent interrupt when a sign-up defers consent.
Both are optional and have no default; supplying tokenCache is what turns
native mode on, and nativeOAuth adds OAuth on top.
| Name | Type | Description |
|---|---|---|
tokenCache | ToriiTokenCache | Secure-storage adapter ({ getToken, saveToken, clearToken }, sync or async). Supplying it switches the SDK to cookie-less native mode. |
nativeOAuth | { browser: { openOAuth: (url: string) => Promise<string | null> }; redirect: string } | Native OAuth config. browser.openOAuth opens url in the system browser and resolves with the deep-link return URL (or null if cancelled); redirect is the deep-link target, which must be in the environment’s Allowed native redirects. Required (as a whole) for OAuth in native mode. |
See also
Section titled “See also”<ToriiProvider>: the full prop reference.- First-party cookies: the web (non-native) way to keep sessions working.