@baukit/auth-native

中文文档
0.18.3发布于 2026/11/07MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 8431

Provider-neutral native OIDC authorization-code client with S256 PKCE, secure-storage and browser-flow ports, and refresh rotation.

原始使用文档
文档目录3 节
  1. Themed login pages
  2. Boundaries
  3. Provider adapters

@baukit/auth-native

Provider-neutral native OIDC authorization-code client with S256 PKCE, standard discovery, secure-storage and browser-flow ports, refresh rotation, and local-first sign-out. The core has no React, router, or product UI dependency.

import { createExpoOidcClient } from '@baukit/auth-native/expo';
import * as AuthSession from 'expo-auth-session';

const auth = createExpoOidcClient({
  issuer: 'https://identity.example.com/tenant',
  clientId: 'product-mobile',
  redirectUri: AuthSession.makeRedirectUri({ scheme: 'product', path: 'oauth' }),
  offlineAccess: true,
});

await auth.initialize();
const result = await auth.signIn();
if (result.status === 'cancelled') {
  // Restore focus or announce cancellation. This is not an authentication error.
}

The issuer is resolved only through /.well-known/openid-configuration; provider-specific paths are never manufactured. A successful authorization-code exchange is followed by an authenticated UserInfo request. Its non-empty sub is the authoritative subject stored in the immutable session. The package does not trust an unverified, locally decoded ID-token claim as identity.

session() exposes the subject, access token, optional refresh and ID tokens, and the issuedAt and expiresAt timestamps in epoch milliseconds. Refresh responses retain the previous refresh token or ID token when the provider omits either. When a session inside the configured refresh window has no refresh token, accessToken() clears it and returns undefined; callers should present sign-in again.

refreshOnRestore makes initialize() refresh an expired stored session before it resolves. A rejected refresh ends the session and a network error keeps it. It defaults to false, and createWorkOsNativeClient sets it to true.

refreshLeewaySeconds defaults to 30 seconds; the refresh window is the smaller of that value and half the token lifetime, measured as expiresAt - issuedAt.

offlineAccess deliberately defaults to false; set it to true only when the provider is configured to issue refresh tokens for offline_access. accessToken() shares one refresh across concurrent callers. Pass { forceRefresh: true } after a 401 to bypass the proactive expiry window while still joining any refresh already in flight.

Terminal refresh rejection (invalid_grant, invalid_token, or HTTP 400/401) clears the session and resolves to undefined. Subscribe with subscribeSessionExpired() to stop schedulers and move UI to signed-out state. Network, malformed-response, rate-limit, and provider 5xx failures preserve the stored session and reject with a sanitized OidcError whose retryable property is true.

subscribeSessionExpired() reports refresh_rejected, refresh_unavailable, or profile_erased. On a backend 401 with code profile_erased, call clearSession({ reason: 'profile_erased' }). It clears the local session and emits the reason once per call. Plain clearSession() emits nothing. OIDC persists a force-login marker under storageKeyPrefix, so the next signIn() requests prompt=login even after a reload. A failed or cancelled login keeps the marker; a successful sign-in removes it. Fresh authentication gives the provider a new auth_time for profile re-onboarding.

signOut() deletes the local session before provider interaction. It then attempts the discovered end-session endpoint. A missing, cancelled, or failing provider logout persists a fail-safe flag so the next signIn() includes prompt=login; that flag is removed only after provider logout or a later successful sign-in. Corrupt secure-storage state, including sessions without issuedAt, is deleted and treated as signed out.

Use safeAuthErrorMessage(error) at UI boundaries. Errors contain only allowlisted library codes/messages and optional HTTP status numbers. Provider bodies, authorization codes, tokens, and adapter exception messages are never copied into errors or logs.

The default Expo entry point uses expo-auth-session, expo-secure-store, and expo-web-browser, which are peer dependencies. For deterministic tests or another native stack, construct NativeOidcClient with your own SecureStoragePort, BrowserFlowPort, fetch, and clock.

Session and force-login keys use dot separators. The default prefix encodes the issuer and client ID with SecureStore-safe characters. A custom storageKeyPrefix must be non-empty and contain only ASCII letters, digits, ., - and _. Construction rejects invalid prefixes before accessing storage. No key rewrite adapter is needed. Old keys are not migrated.

Universal Expo products can pass a storage port to createExpoOidcEnvironment or createExpoOidcClient. This supports a web localStorage adapter or a product-owned compatibility/migration wrapper while retaining the standard Expo browser flow.

Themed login pages

A login theme can match the app's appearance when the app tells it the theme through the OAuth state. Pass the segments to signIn and give the Expo flow an entropy source:

import { appearanceStateDecoration } from '@baukit/auth-native';
import { createExpoOidcClient } from '@baukit/auth-native/expo';
import * as Crypto from 'expo-crypto';

const auth = createExpoOidcClient(config, {
  randomBytes: (size) => Crypto.getRandomBytesAsync(size),
});

await auth.signIn({ stateDecoration: appearanceStateDecoration({ mode: 'dark' }) });

The state becomes ap1.d.<64 hex characters>, or ap1.d.<PRIMARY>.<SECONDARY>.<nonce> when you pass both #RRGGBB colors. decoratedAuthorizationState needs at least 32 random bytes and accepts only ASCII letters and digits per segment. The nonce keeps the state unguessable, and the client still compares the returned state with the one it sent.

Decoration never blocks sign-in. Without randomBytes, or when the entropy source throws or a segment is malformed, the flow uses AuthSession's own state and the login page shows its default theme. createExpoBrowserFlow is the same flow on its own, for products that build their NativeOidcEnvironment by hand. Other BrowserFlowPort implementations may ignore stateDecoration.

The state travels in the authorization URL, so the provider and its access logs see it. Put appearance hints there, never user data. The Keycloak theme from the baukit CLI's OIDC template decodes the ap1 format.

Boundaries

The package owns the native OIDC flow, session storage, and refresh. It ships no screens, no navigation, and no authorization logic; what a signed-in user may do is decided by the product and enforced by the server.

The core has no React, router, or product UI dependency, and the Expo entry point is a separate subpath. Construct NativeOidcClient with your own SecureStoragePort, BrowserFlowPort, fetch, and clock when you are on another native stack or want a deterministic test.

@baukit/auth-web is the same contract for browsers.

Provider adapters

@baukit/auth-native/clerk-expo creates a ClerkNativeClient and its React Provider together. Install the optional @clerk/expo and React peers, apply Clerk's Expo config plugin, and wrap the product in the returned provider. Supply the publishable key and SecureStoragePort. The bridge uses Clerk's hosted sign-in and token cache. @baukit/auth-native/clerk exports the client and SDK port separately for other integrations.

@baukit/auth-native/workos exports createWorkOsNativeClient. Supply the AuthKit public client ID and the existing Expo browser/storage environment. It runs S256 PKCE through the browser port and exchanges tokens using WorkOS's JSON public-client API. It stores refresh tokens in secure storage and clears them before ending the provider session. No API key belongs in the app. Provider clients share initialize, session, subscribe, accessToken, signIn, signOut, clearSession and subscribeSessionExpired with OIDC.

Native WorkOS uses NativeOidcClient and inherits the persisted prompt=login request. Clerk emits profile_erased and clears the local session, but its SDK bridge does not persist a force-login marker or force fresh authentication. Clerk needs a JWT template with auth_time for re-onboarding and fresh provider authentication before the backend can admit an erased subject.

版本与发布记录

@baukit/auth-native 共发布 67 个版本,首次发布于 2026/08/26;过去 12 个月发布了 67 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.18.3latest2026/11/07今天
0.18.22026/11/061 天前
0.18.12026/11/062 天前
0.18.02026/11/052 天前
0.17.12026/11/034 天前
0.17.02026/11/025 天前

关于 @baukit/auth-native 的常见问题

@baukit/auth-native 是什么?

Provider-neutral native OIDC authorization-code client with S256 PKCE, secure-storage and browser-flow ports, and refresh rotation.

如何安装 @baukit/auth-native?

在项目目录运行 npm install @baukit/auth-native,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:expo-web-browser(^57.0.3)、expo-auth-session(^57.0.13)、expo-secure-store(^57.0.4)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@baukit/auth-native 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@baukit/auth-native 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@baukit/auth-native 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@baukit/auth-native 可以商用吗?

@baukit/auth-native 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@baukit/auth-native 的下载量有多少?

根据 npm 官方下载统计,@baukit/auth-native 在 2026/10/08 – 2026/11/06 这 30 天内被下载 8,431 次,日均约 281 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@baukit/auth-native 还在维护吗?

最新版本 0.18.3 发布于 2026/11/07(今天);至今共发布 67 个版本,其中过去 12 个月发布了 67 个;首次发布于 2026/08/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。