---
title: "Native and multi-account authentication"
description: "Compose native credential storage, refresh ownership, account selection, and recovery without confusing adapters with device-wide coordination."
tags: ["auth","native","expo","accounts","composition"]
---

Use the injectable [React auth transport](/docs/packages/identity/auth-react) with a product-owned native coordinator. `createExpoTokenStorage` stores credentials; it is not a refresh coordinator or a complete Expo/Tauri transport. The browser bearer transport's refresh cookie is not a native token-pair implementation.

## Server authority

Choose strict refresh replay explicitly when required; bounded grace remains the default. Authorization-code exchange binds the selected account, client, redirect URI, PKCE challenge, scopes, and expiry. It locks and revalidates the code, runs live authorization, issues the session, and records consumption in the same transaction, returning credentials only after commit. Qualify that physical transaction with your adapters; equal transaction-kind strings are insufficient.

An ambiguous commit does not establish that the code remains unused. Require a reviewed recovery flow or a new login instead of automatically repeating exchange. Account selection never bypasses live membership or an account-bound credential's scope. Switching between accounts for one user need not rotate credentials; switching identities requires separate credential slots and state generations.

## One refresh owner per credential family

Coordinate every window, process, and background task that can use the family. A mutex in one hook or JavaScript runtime is not device-wide ownership. Desktop applications can designate a native/main-process owner reached by IPC; mobile applications must qualify foreground/background coordination and secure storage.

| Event | Host behavior |
| --- | --- |
| Login or callback | Bind state, PKCE, redirect, and pending-login generation; canceled or superseded work cannot install a late token pair. |
| Concurrent refresh | One owner submits; waiters share its result. Do not replay ambiguous network outcomes. |
| Refresh succeeds | Validate and persist the complete new pair before publishing success, only for the current generation. |
| Storage write fails | Gate further refresh with the old token; rotation may already have committed. Require recovery. |
| Identity switch | Fence in-flight UI results by generation and preserve other identities' credential slots. |
| Logout | Stop admission, advance the generation, coordinate outstanding work, and perform revocation/deletion. A late response cannot restore the slot. |
| Deletion fails or app restarts | Report failure and recover pending journal state before admitting more refresh. Empty UI state is not proof of revocation. |

The secure-storage adapter has no journal, multi-key transaction, cross-process lock, or automatic network retry. Several IPC calls do not form one transaction. Keep secrets out of SSR props, logs, and synchronization notices; use opaque slot identifiers instead of emails or tokens.

## Qualification boundary

Package adapter tests cover slot isolation and malformed/read/write/delete failures with injected storage. Provider race tests cover instance-local ownership, not native lifecycle coordination. Before deployment, test login/cancel/restart/logout, delayed refresh, two windows, background suspension, account switching, callback replay, storage failures, and ambiguous network outcomes on the actual runtimes. This is a composition contract, not a claim that a packaged device-wide coordinator ships with Playstack.
