React SDK (@auth-platform/react)#

@auth-platform/react は @auth-platform/browser を React hooks、状態 controller、headless render-prop component として公開する。UI と styling はアプリ側で構成する。

セットアップ#

pnpm add @auth-platform/react @auth-platform/browser react react-dom
pnpm add -D @types/react @types/react-dom

React 19 以上が peer dependency である。Browser client は component render ごとに生成せず、module scope などで一度だけ作り、AuthProvider に渡す。

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

createRoot(document.getElementById("root")!).render(
  <AuthProvider client={authClient}><App /></AuthProvider>,
);

AuthProvider の props は client: BrowserAuthClient と optional children?: ReactNode である。同じ client の間は sign-in flow、profile、session-list controller を保持し、client を差し替えると新しい controller 群を作る。すべての public hook と headless component は provider の内側で使う。provider 外の hook 呼び出しは Error になる。

Browser SDK を使う Client の設定では、redirect_uris に callback URI を、allowed_origins に SPA の origin を登録する。baseUrl には Browser SDK の初期化手順に示す Interaction Domain の origin を指定する。Browser client の既定 scope は openid profile email である。refreshSession()、restoreSession()、期限切れ時の自動 refresh に refresh token を使うには、scope に offline_access を含め、Client の allowed_scopes にも登録する。既定の scope では 5 分の access token 有効期限後に再認可が必要になる。設定の詳細は Browser SDK を参照する。

export interface AuthProviderProps {
  readonly client: BrowserAuthClient;
  readonly children?: ReactNode;
}

Hosted Login へ OIDC redirect するには useAuthClient() が返す browser client の beginHostedAuthorization() を呼ぶ。useAuth().begin() は Hosted Login redirect ではなく、React 内で email OTP/passkey を進める transaction-based headless flow を開始する。redirect callback は Browser SDK の client と同じ sessionStorage state により page reload 後に再開できる。

Hooks#

useAuthClient() / useAuthState()#

Hook Return
useAuthClient(): BrowserAuthClient provider に渡した raw browser client。Hosted redirect、public config、独自 client method を使う advanced API
useAuthState(): BrowserAuthSnapshot `{ status: "signed_out"

useAuth()#

UseAuthResult は auth snapshot と transaction-driven sign-in flow の facade である。

Field Signature / 型
snapshot BrowserAuthSnapshot
isSignedIn boolean
flowState SignInFlowState。idle, starting, awaiting_method, email_challenge_sent, ceremony, completing, signed_in, failed
pendingAction PendingAction: begin, send_email, resend_email, verify_email, passkey, social, cancel, null のいずれか
flowError BrowserAuthError | null
begin() Promise<Outcome<BrowserTransaction, SignInFlowError>>
sendEmailCode(email: string) Promise<Outcome<{ challengeId: string }, SignInFlowError>>
resendEmailCode() Promise<Outcome<{ challengeId: string }, SignInFlowError>>
verifyEmailCode(code: string) Promise<Outcome<AuthenticatedSession, SignInFlowError>>。verify → complete → code exchange を行う
signInWithPasskey() Promise<Outcome<AuthenticatedSession, SignInFlowError>>
registerPasskey(input?: { readonly name?: string }) Promise<Outcome<AuthenticatedSession, SignInFlowError>>
signInWithSocial(provider: SocialProvider) Promise<Outcome<{ redirected: true }, SignInFlowError>>。トップレベル遷移後は browser client の callback handler を呼ぶ
cancelSignIn() Promise<Outcome<{ cancelled: true }, SignInFlowError>>
resetSignIn() void。flow を local-only で idle に戻す
getAccessToken() Promise<Outcome<string, BrowserAuthError>>
refreshSession() Promise<Outcome<AuthenticatedSession, BrowserAuthError>>
restoreSession() Promise<Outcome<AuthenticatedSession, BrowserAuthError>>
signOut() Promise<Outcome<{ readonly signedOut: true }, BrowserAuthError>>
handleRedirectCallback(callbackUrl: string) Promise<Outcome<AuthenticatedSession, BrowserAuthError>>

SignInFlowError は BrowserAuthError | InvalidFlowStateError である。flow state と合わない呼び出し (例: email challenge 前の verifyEmailCode) は invalid_flow_state outcome であり、wire error と区別できる。

useEmailOtp() / usePasskey() / useSocialSignIn()#

Hook Return fields
useEmailOtp(): UseEmailOtpResult challenge: EmailOtpChallenge | null (challengeId: string, email: string, expiresAt: string); pendingAction: PendingAction; error: BrowserAuthError | null
send(email: string) Promise<Outcome<{ challengeId: string }, SignInFlowError>>
resend() Promise<Outcome<{ challengeId: string }, SignInFlowError>>
verify(code: string) Promise<Outcome<AuthenticatedSession, SignInFlowError>>
usePasskey(): UsePasskeyResult isSupported: boolean; isCeremonyActive: boolean; pendingAction: PendingAction; error: BrowserAuthError | null
signInWithPasskey() Promise<Outcome<AuthenticatedSession, SignInFlowError>>
registerPasskey(input?: { readonly name?: string }) Promise<Outcome<AuthenticatedSession, SignInFlowError>>
useSocialSignIn(): UseSocialSignInResult signIn(provider: SocialProvider): Promise<Outcome<{ redirected: true }, SignInFlowError>>; pending: boolean; error: BrowserAuthError | null

usePasskey().isSupported が false の場合は passkey UI を表示しない。Social sign-in は iframe/WebView に埋め込まずトップレベル redirect を使う。

useUser() / useSession()#

Hook / field Return type
useUser(): UseUserResult status: UserProfileStatus (`"idle"
reload() Promise<Outcome<MeUser, BrowserAuthError>>
update(patch: MeUpdateRequest) Promise<Outcome<MeUser, BrowserAuthError>>
useSession(): UseSessionResult snapshot: BrowserAuthSnapshot; status: SessionListStatus (`"idle"
reload() Promise<Outcome<readonly MeSessionSummary[], BrowserAuthError>>
revoke(sessionId: string) Promise<Outcome<{ revoked: true }, BrowserAuthError>>
revokeAll() Promise<Outcome<{ revoked: true }, BrowserAuthError>>
refreshSession() Promise<Outcome<AuthenticatedSession, BrowserAuthError>>
signOut() Promise<Outcome<{ readonly signedOut: true }, BrowserAuthError>>

useUser は signed-in transition で profile を読み込み、signed-out で cache を消す。useSession も signed-in 中に session list を読み込む。profile update は email 変更を行わない。email 変更は browser client の OTP-based email-change methods を使う。

Headless render-prop components#

各 component は DOM や style を描画せず、children render function に状態と handler を渡す。hook と同様、AuthProvider の内側で使う。

EmailOtpForm#

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

function SignInForm() {
  return (
    <EmailOtpForm onSignedIn={(session) => console.log(session.scope)}>
      {({ step, email, setEmail, code, setCode, send, resend, verify,
          pendingAction, error, challengeExpiresAt }) => (
        <form onSubmit={(event) => {
          event.preventDefault();
          if (step === "enter_email") send();
          else verify();
        }}>
          {step === "enter_email" ? (
            <input type="email" value={email} onChange={(event) => setEmail(event.target.value)} />
          ) : (
            <>
              <input inputMode="numeric" value={code} onChange={(event) => setCode(event.target.value)} />
              <button type="button" onClick={resend}>再送</button>
              <time>{challengeExpiresAt}</time>
            </>
          )}
          <button disabled={pendingAction !== null}>
            {step === "enter_email" ? "コード送信" : "確認"}
          </button>
          {error && <p role="alert">{error.type}</p>}
        </form>
      )}
    </EmailOtpForm>
  );
}
Props / render props Signature
onSignedIn? (session: AuthenticatedSession) => void
children (props: EmailOtpFormRenderProps) => ReactNode
step `"enter_email"
email, code string; setEmail(value: string), setCode(value: string)
send, resend, verify () => void。verify は成功時に transaction の complete と code 交換まで実行し、onSignedIn を呼ぶ
pendingAction `"send_email"
error `BrowserAuthError
challengeExpiresAt `string

Component が email と code の field value を保持する。送信中の disabled state、error UI、expiry 時刻の見せ方は render function が決める。handler は void を返し、結果状態は flow の render props に反映される。

PasskeyButton#

Props / render props Signature
mode? `"sign-in"
name? register 時に authenticator label として渡す string
onComplete? (session: AuthenticatedSession) => void
children (props: PasskeyButtonRenderProps) => ReactNode
render props onClick(): void, disabled, isSupported, pending, error: BrowserAuthError | null

disabled は ceremony 中または passkey 非対応時に true となる。passkey 非対応なら render function は UI を隠すことができる。onClick は完全な sign-in/registration ceremony を開始し、成功時だけ onComplete を呼ぶ。

SocialButtons#

Props / render props Signature
providers readonly SocialProvider[]
children (props: SocialButtonsRenderProps) => ReactNode
render props 同じ providers; signIn(provider: SocialProvider): void; pending; error: BrowserAuthError | null

表示する provider は getPublicConfig().available_methods とアプリの要件に合わせて選ぶ。signIn handler は provider 向けのトップレベル redirect を開始する。

React-free controllers#

Controller factory は React context/provider を必要としない。状態 machine をテストや独自 UI から直接駆動する場合に使う。

createSignInFlow(client: SignInFlowClient): SignInFlow#

SignInFlowClient は BrowserAuthClient のうち createTransaction, cancelTransaction, startEmailOtp, resendEmailOtp, verifyEmailOtp, startPasskeyRegistration, startPasskeyAuthentication, startSocialSignIn, completeTransaction, exchangeAuthorizationCode を要求する。返る SignInFlow API は次のとおり。

Method Signature
getSnapshot() SignInFlowSnapshot (state, pendingAction)
subscribe(listener: SignInFlowListener): () => void listener を登録し、戻り値で解除する
begin() Promise<Outcome<BrowserTransaction, SignInFlowError>>
sendEmailCode(email: string) / resendEmailCode() Promise<Outcome<{ challengeId: string }, SignInFlowError>>
verifyEmailCode(code: string) Promise<Outcome<AuthenticatedSession, SignInFlowError>>
signInWithPasskey() / registerPasskey(input?: { readonly name?: string }) Promise<Outcome<AuthenticatedSession, SignInFlowError>>
signInWithSocial(provider: SocialProvider) Promise<Outcome<{ redirected: true }, SignInFlowError>>
cancel() Promise<Outcome<{ cancelled: true }, SignInFlowError>>
reset() void。server call を行わず idle に戻す

SignInFlowState は idle → starting → awaiting_method → email_challenge_sent | ceremony → completing → signed_in の進行状態と failed を表す。PendingAction は実行中 action、SignInFlowSnapshot は state と pendingAction を持つ。Social redirect で別 page に移ると React flow instance の UI state は失われる。callback 後の code exchange は handleHostedAuthorizationCallback() が処理する。

createUserProfileController(client: UserProfileClient): UserProfileController#

UserProfileClient = Pick<BrowserAuthClient, "getMe" | "updateMe">。snapshot は { status: "idle" | "loading" | "loaded" | "error", user: MeUser | null, error: BrowserAuthError | null } である。

Method Signature
getSnapshot() UserProfileSnapshot
subscribe(listener: UserProfileListener): () => void listener を登録し、戻り値で解除
load() Promise<Outcome<MeUser, BrowserAuthError>>。同時 load は一 request に集約する
update(patch: MeUpdateRequest) Promise<Outcome<MeUser, BrowserAuthError>>
clear() void。cached profile を idle に戻す

not_authenticated は error status ではなく idle に戻る。その他の失敗は以前取得した user を残し、error status にする。

createSessionListController(client: SessionListClient): SessionListController#

SessionListClient = Pick<BrowserAuthClient, "listMySessions" | "revokeMySession" | "revokeAllMySessions">。snapshot は { status: "idle" | "loading" | "loaded" | "error", sessions: readonly MeSessionSummary[] | null, error: BrowserAuthError | null } である。

Method Signature
getSnapshot() SessionListSnapshot
subscribe(listener: SessionListListener): () => void listener を登録し、戻り値で解除
load() Promise<Outcome<readonly MeSessionSummary[], BrowserAuthError>>。同時 load は一 request に集約する
revoke(sessionId: string) Promise<Outcome<{ revoked: true }, BrowserAuthError>>。成功時は cache から該当 entry を除く
revokeAll() Promise<Outcome<{ revoked: true }, BrowserAuthError>>。成功時は local list を idle に戻す。現在の session も server 側で revoke される
clear() void。cached session list を idle に戻す

関連資料#