React Native SDK (@auth-platform/react-native)#

@auth-platform/react-native は Expo / React Native アプリに Email OTP、Passkey、ソーシャルログイン、セッション管理を提供し、Expo Config Plugin を同梱する。

対応環境と依存関係#

Expo SDK 55 以降、React Native New Architecture を有効にした Expo Development Build または本番ビルドを使う。iOS は 15.1 以降、Android は API 28 以降かつ compile SDK 34 以降が必要である。Expo Go は Passkey、独自 scheme、Associated Domains / App Links を含む本番相当の動作確認には使えない。Bare React Native では Expo Modules を導入する。

SDK の peer dependency と宣言範囲は次のとおりである。互換性のある native module は Expo SDK に合わせて揃える。

peer dependency SDK の peer 範囲
react >=19
react-native >=0.79
expo >=55
expo-secure-store >=13
expo-web-browser >=14
expo-auth-session >=6
expo-linking >=7
expo-crypto >=14
react-native-passkeys >=0.3
pnpm add @auth-platform/react-native

ネイティブ module や Config Plugin を追加・変更した後は Development Build を再生成・再インストールする。JavaScript の Fast Refresh だけではネイティブ設定や module は反映されない。

クライアントの作成#

issuer は Environment の Canonical Issuer を指定する。形式は https://<issuer-host>/e/<environmentId> であり、ログイン画面を配信する Interaction Domain ではない。redirectUri はクライアントへ登録する値と完全一致させる。React Native クライアントは public client であり、アプリに client secret を含めない。SDK は PKCE を使う。

import { createReactNativeAuthClient } from "@auth-platform/react-native";

export const authClient = createReactNativeAuthClient({
  issuer: "https://id.zeroword.smartcrab.ai/e/<environmentId>",
  clientId: "<clientId>",
  redirectUri: "com.customer.app:/oauth/callback",
  scopes: ["openid", "profile", "email", "offline_access"],
  rpDomain: "auth.customer.example",
});

scopes を省略すると openid profile email offline_access を使う。resource には Resource Indicator を 1 つまたは複数指定できる。rpDomain は Passkey の rp_id を固定する値で、scheme や path を含まないホスト名を指定する。environmentId は issuer からの Environment ID 抽出を上書きする場合だけ指定し、通常は省略する。fetch、now、およびネイティブ port も差し替え可能であり、通常のアプリでは既定実装を使う。

作成後に authClient.configure(config) を呼ぶ形も使える。configure() より前にクライアントメソッドや hook を呼ぶと not_configured になる。

クライアント API#

失敗時、非同期 client API の Promise は ReactNativeAuthClientError で reject する。configure() は同期 API であり、不正な issuer 設定などでは通常の Error を throw する。OTP 検証や Passkey 検証の応答から access token を直接返すのではなく、SDK が authorization code を token endpoint へ交換する。

メソッド 用途
getSession() セッション metadata を返す。未認証なら null。access token 自体は返さない
getAccessToken(input?) 有効な access token を返し、期限が近ければ refresh する。{ forceRefresh: true } で明示的に refresh できる
sendEmailCode({ email }) Email OTP challenge を作成し、challengeId と有効期限を返す
verifyEmailCode({ challengeId, code }) 対応する challenge を検証し、認証 code 交換まで実行する
signInWithPasskey() Passkey でサインインする
registerPasskey({ name? }) 認証済みユーザーに Passkey を登録する
signInWithSocial({ provider }) system browser で social sign-in する
linkSocialAccount({ provider }) 認証済みアカウントへ social identity を link する
getMe() / updateMe(input) /v1/me のユーザープロフィールを取得・更新する
listPasskeys() / deletePasskey(passkeyId) Passkey を一覧・削除する
listSessions() / revokeSession(sessionId) 他の device session を一覧・失効する
signOut() refresh token revoke を試み、network の成否にかかわらずローカル session を消去する
signOutAll() /v1/me/sessions による全 session revoke を試み、その後ローカル session を消去する
subscribe(listener) / getSnapshot() 認証状態変更を購読・同期取得する
configure(config) クライアントの設定を行う

Email OTP の headless flow は、challenge 作成と検証を分けて呼び出す。メールアドレスと入力 code はアプリの UI から渡す。

export async function startEmailOtp(email: string) {
  return authClient.sendEmailCode({ email });
}

export async function finishEmailOtp(challengeId: string, code: string) {
  const result = await authClient.verifyEmailCode({ challengeId, code });
  const accessToken = await authClient.getAccessToken();
  return { result, accessToken };
}

sendEmailCode は challengeId と有効期限を返す。verifyEmailCode 成功時、SDK が authorization code を token endpoint へ交換し、refresh token を保存する。

実際のアプリでは code をメールから受け取り、error や rate limit を UI で扱う。signInWithSocial({ provider }) と linkSocialAccount({ provider }) では provider に google、apple、github、microsoft、generic_oidc を指定する。利用できる provider は Environment 側の設定に従う。

ソーシャル認証は expo-web-browser の auth session で system browser を開き、ephemeral session を優先する。signInWithSocial の callback は設定した redirectUri に戻り、SDK が state を検証して authorization code を交換する。iOS では custom-scheme callback を使う。iOS の AASA は webcredentials のみを提供し、applinks を含まないため、Universal Link callback は提供しない。Android の App Links association file は delegate_permission/common.handle_all_urls を含む。埋め込み WebView は使わない。

/v1/me 系で SDK が提供する操作は、プロフィール、Passkey、device session、social identity link である。Public Authentication API のその他の操作(identity 管理、email 変更、アカウント削除など)は Public Authentication API を参照する。

React Provider と hooks#

設定済み client をアプリの root で AuthProvider に渡す。Provider は client の作成・configure やアプリ状態の所有はせず、Context を提供する。

import { AuthProvider } from "@auth-platform/react-native";
import { authClient } from "./auth-client";

export function Root() {
  return (
    <AuthProvider client={authClient}>
      <App />
    </AuthProvider>
  );
}

公開 hook は次のとおりである。

hook 主な用途
useAuth() 認証 snapshot、Email OTP / Passkey / social sign-in、token 取得、sign-out
useSession() 現在のセッション metadata
useUser() /v1/me プロフィールの読込・更新
usePasskey() Passkey 認証、登録、一覧・削除。端末で未対応なら passkeySupported が false
useEmailOtp() Email OTP を送信・検証
useSocialSignIn() Social sign-in と identity link
useSessionList() device session の一覧・失効

AuthProvider の外で hook を使うとエラーになる。操作 Promise は呼び出し元へ reject されるため、UI の event handler で ReactNativeAuthClientError を処理する。useUser、usePasskey、useSessionList は状態として error も公開する。

Expo Config Plugin と callback 設定#

package には app.plugin.js の entry shim があり、Config Plugin の export は @auth-platform/react-native/plugin である。Expo config の plugins へ一度だけ追加する。

export default {
  name: "customer-app",
  slug: "customer-app",
  scheme: "com.customer.app",
  ios: {
    bundleIdentifier: "com.customer.app",
    appleTeamId: "ABCDE12345",
  },
  android: { package: "com.customer.app" },
  plugins: [
    [
      "@auth-platform/react-native/plugin",
      {
        clientId: "<clientId>",
        rpDomain: "auth.customer.example",
        callbackPath: "oauth/callback",
      },
    ],
  ],
};

scheme は reverse-DNS 形式で必須である。plugin options は次のとおりである。

option 意味
clientId Environment に登録する React Native client ID
rpDomain RP ID にする bare hostname。scheme / path を含めない
callbackPath callback の relative path。先頭の / は含めない

Plugin は iOS entitlements へ webcredentials:<rpDomain> と applinks:<rpDomain> を追加し、expo.scheme を CFBundleURLTypes へ登録する。Android では custom-scheme callback の intent filter、https://<rpDomain>/<callbackPath> 向けの autoVerify App Link filter、Credential Manager が読む asset_statements resource と Manifest metadata を追加する。resource は https://<rpDomain>/.well-known/assetlinks.json を参照する。iOS の AASA は webcredentials のみを提供し、applinks callback は提供しない。iOS では custom-scheme callback を使う。Android の App Links association file は delegate_permission/common.handle_all_urls を含む。

Plugin は scheme / options と、明示された最低対応未満の build 設定を build time に拒否する。Plugin 自体は iOS deployment target や Android SDK version を設定せず、AASA / assetlinks も生成・登録しない。対応下限は iOS 15.1、Android min SDK 28、compile SDK 34 であり、アプリ側の Expo / native build 設定で満たす。iOS Team ID、Bundle ID、Android package 名、署名 fingerprint は次節のとおり Environment へ登録する。

Mobile App の登録#

Mobile App の native association には、scheme、iOS Bundle ID と Apple Team ID、Android package name と SHA-256 署名 fingerprint が必要である。auth-platform react-native configure は Expo config から識別子を読み取り、署名 fingerprint を取得し、Management API の client へ登録して AASA / assetlinks と callback URI を確認する。CLI は繰り返し実行できる。option を含む手順は CLI を参照する。

Management API で管理する場合は PATCH /v1/clients/{clientId} の mobile_app に次の値を登録する。API key には該当する Management scope が必要である。

mobile_app field 登録する値
app_scheme reverse-DNS 形式の custom scheme
ios_bundle_id iOS Bundle ID
ios_team_id Apple Team ID
android_package_name Android package name
android_sha256_fingerprints 署名証明書の SHA-256 fingerprint。Debug / 配布用証明書ごとに登録する
universal_link_host rpDomain に指定する RP ID

詳細は Management API を参照する。

Secure storage とアプリ lifecycle#

既定の SecureStorePort は expo-secure-store を使う。SecureStore に保存するのは refresh token、token family ID、user ID / scope 等の最小限の session metadata、および認証 transaction 中だけ必要な PKCE verifier / state / transaction ID と関連する一時 metadata である。Email OTP はアプリ再起動後も検証を続けられるよう、pending transaction を完了するまで一時保存する。完了や検証失敗などで flow を閉じると消去する。access token は memory 内だけに置き、user profile 全体、provider access token、OTP は永続化しない。保存 key は Environment ID と client ID で分離される。iOS の既定保存属性は AFTER_FIRST_UNLOCK である。

access token の期限が 60 秒未満になると refresh する。同じ Environment ID / client ID の SecureStore key を共有する client instance 間では process-wide で進行中の refresh を共有し、refresh token replay を避ける。アプリが background / inactive から foreground へ戻ると token expiry を確認する。network 復帰時の refresh には networkStatus port の注入が必要であり、省略時は foreground 復帰だけが trigger になる。invalid_grant による refresh 失敗では local session を削除し、一時的な network failure では即時に削除しない。signOut() は refresh token の revoke を試みた後、network の成否にかかわらずローカル session を消去する。

Native adapter 境界とエラー#

SDK は native module の型を公開面へ漏らさず、次の port で差し替え可能にする。

port 契約と標準実装
PasskeyNativeAdapter isSupported(): Promise<boolean>、create(optionsJson: string): Promise<string>、get(optionsJson: string): Promise<string>。native credential は JSON string で受け渡し、標準実装は react-native-passkeys
SecureStorePort getItemAsync(key): Promise<string | null>、setItemAsync(key, value): Promise<void>、deleteItemAsync(key): Promise<void>。標準実装は expo-secure-store
WebBrowserPort openAuthSessionAsync(url, redirectUrl): Promise<WebBrowserAuthResult>。success 時は redirect URL を返し、標準実装は ephemeral session を優先する expo-web-browser auth session
LinkingPort parse(url: string): ParsedCallbackUrl、getInitialURL(): Promise<string | null>、addEventListener(handler): () => void。標準実装は expo-linking
CryptoPort getRandomBytes(byteCount): Uint8Array と sha256Base64(data): Promise<string>。標準実装は expo-crypto
AppStatePort 現在の app state と change event を提供する。subscription は remove() で解除する。標準実装は React Native AppState
NetworkStatusPort subscribe(listener: (online: boolean) => void): () => void で online / offline 遷移を提供する。SDK に既定実装はなく、必要ならアプリが注入する

失敗は ReactNativeAuthClientError として reject される。判別には message ではなく .code.type を使う。

code.type 意味
network_error network failure。retryable: true
invalid_response server 応答が不正
rate_limited rate limit。retryAfterSeconds がある場合は待機時間を示す
problem_details Public Authentication API の RFC 9457 problem details
oauth_error OAuth / OIDC error
state_mismatch callback の state が一致しない
not_authenticated 認証が必要な操作に未認証でアクセスした
passkey_not_supported 端末で Passkey を利用できない
passkey_error Passkey 操作失敗。reason は cancelled、failed、in_progress
social_cancelled system browser で social sign-in を完了せず戻った
invalid_flow_state 対応する pending OTP transaction がない
not_configured configure 前に client を使用した

UI#

UI component は提供しない。公開 SDK の hooks を使ってアプリ固有の UI を実装する。