SDK

React Native SDK

@authon/react-nativeReact Native / Expo 向けのフック、ソーシャルボタン、安全なトークン保存、低レベル OAuth ヘルパーを提供します。

インストール

`expo-secure-store` をトークン永続化に使います。推奨 OAuth フローには `expo-web-browser` が必須です。

bash
npm install @authon/react-native react-native-svg
npx expo install expo-secure-store expo-web-browser

Provider 設定

App.tsx
import { AuthonProvider } from "@authon/react-native";
import * as SecureStore from "expo-secure-store";

const storage = {
  getItem: (key: string) => SecureStore.getItemAsync(key),
  setItem: (key: string, value: string) => SecureStore.setItemAsync(key, value),
  removeItem: (key: string) => SecureStore.deleteItemAsync(key),
};

export default function App() {
  return (
    <AuthonProvider publishableKey="pk_live_..." storage={storage}>
      <Navigation />
    </AuthonProvider>
  );
}

メール / パスワード

screens/LoginScreen.tsx
import { useState } from "react";
import { View, TextInput, Button, Text, ActivityIndicator } from "react-native";
import { useAuthon, useUser } from "@authon/react-native";

export function LoginScreen() {
  const { isLoaded } = useUser();
  const { signIn, signOut, user, isSignedIn } = useAuthon();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const handleSignIn = async () => {
    setLoading(true);
    setError(null);
    try {
      await signIn({ strategy: "email_password", email, password });
    } catch (err: any) {
      setError(err.message ?? "Sign-in failed");
    } finally {
      setLoading(false);
    }
  };

  if (!isLoaded) return <ActivityIndicator />;

  if (isSignedIn) {
    return (
      <View style={{ padding: 24, gap: 12 }}>
        <Text>Welcome, {user?.displayName ?? user?.email}</Text>
        <Button title="Sign out" onPress={signOut} />
      </View>
    );
  }

  return (
    <View style={{ padding: 24, gap: 12 }}>
      <TextInput placeholder="Email" value={email} onChangeText={setEmail} autoCapitalize="none" />
      <TextInput placeholder="Password" value={password} onChangeText={setPassword} secureTextEntry />
      {error ? <Text style={{ color: "red" }}>{error}</Text> : null}
      <Button title={loading ? "Signing in..." : "Sign in"} onPress={handleSignIn} disabled={loading} />
    </View>
  );
}

ソーシャルボタン

tsx
import { SocialButtons } from "@authon/react-native";

export function SocialLoginSection() {
  return (
    <SocialButtons
      onSuccess={() => console.log("Signed in")}
      onError={(error) => console.error(error)}
    />
  );
}

推奨 Expo OAuth フロー

推奨フローは `flow=redirect` で `expo-web-browser` セッションを開き、HTTPS ブリッジページ経由でアプリに戻る方式です。チェーン全体が HTTP 3xx リダイレクトで構成されるべきです。ブリッジページで JS リダイレクトに依存すると、Android Custom Tab でブラウザが閉じない問題が発生します。

GoogleButton.tsx
import * as WebBrowser from "expo-web-browser";
import { Alert, Button } from "react-native";
import { useAuthon } from "@authon/react-native";

const API_URL = "https:0
const PUBLISHABLE_KEY = "pk_live_...";
const APP_DEEP_LINK = "myapp://oauth-callback";

// HTTPS bridge page — Authon redirects here via HTTP 3xx,
// then this page redirects to your app deep link.
const RETURN_TO = "https:4

async function requestOAuthUrl(provider: string) {
  const params = new URLSearchParams({
    redirectUri: 11,
    flow: "redirect",
    returnTo: RETURN_TO,
  });

  const response = await fetch(
    12,
    { headers: { "x-api-key": PUBLISHABLE_KEY } },
  );

  if (!response.ok) {
    throw new Error(await response.text());
  }

  return response.json() as Promise<{ url: string; state: string }>;
}

export function GoogleButton() {
  const { completeOAuth, getToken, signOut } = useAuthon();

  const handlePress = async () => {
    try {
      const { url, state } = await requestOAuthUrl("google");

      5
      const pollPromise = completeOAuth(state);

      6
      await WebBrowser.openAuthSessionAsync(url, APP_DEEP_LINK);

      7
      await pollPromise;

      const authonAccessToken = getToken();
      8
    } catch (err: any) {
      9
      10
      Alert.alert("Login failed", err.message ?? "OAuth error");
    }
  };

  return <Button title="Continue with Google" onPress={handlePress} />;
}

HTTPS ブリッジページ

ブリッジページは Authon リダイレクトから `authon_oauth_state` を受け取り、アプリのディープリンクへ転送します。JS `window.location.replace` ではなくサーバーサイド HTTP 302 リダイレクトを使ってください。iOS/Android 両方でブラウザ自動終了の安定性に最も重要な要素です。

mobile-callback
<!-- Recommended: HTTP redirect (server-side) -->
<!-- Your server at /authon/mobile-callback should do: -->
<!-- 302 redirect to: myapp://oauth-callback?state=xxx -->
<!-- This is more reliable than JS-based redirect on Android Custom Tabs. -->

<!-- If you cannot do server-side redirect, use this HTML fallback: -->
<!doctype html>
<html>
  <body>
    <script>
      const params = new URLSearchParams(window.location.search);
      const state = params.get("authon_oauth_state");
      const error = params.get("authon_oauth_error");

      const target = new URL("myapp://oauth-callback");
      if (state) target.searchParams.set("state", state);
      if (error) target.searchParams.set("error", error);

      // Fallback: JS redirect. Less reliable on Android Custom Tabs.
      window.location.replace(target.toString());
    </script>
  </body>
</html>

サインアウト(重要)

サインアウト時は必ず `signOut()` を呼び出してください。ローカルトークンと Authon サーバーセッションの両方をクリアします。複数の OAuth プロバイダーを切り替える場合(例: Google → Kakao)、Authon セッションをクリアしないと次のログインで古いセッションが干渉します。

LogoutButton.tsx
import { useAuthon } from "@authon/react-native";

function LogoutButton() {
  const { signOut } = useAuthon();

  const handleLogout = async () => {
    // signOut() calls DELETE /v1/auth/signout on Authon,
    // clearing the Authon session + local tokens.
    // This is critical when switching between providers
    // (e.g. Google → Kakao) to avoid stale session conflicts.
    await signOut();

    // If your app has its own backend session, clear it too:
    // await myBackend.post("/api/logout");
  };

  return <Button title="Sign out" onPress={handleLogout} />;
}

アカウント削除

Authon は `DELETE /v1/auth/me` でユーザーが自分のアカウントを削除できます。SDK では `deleteAccount()` として利用でき、Apple App Store と Google Play のポリシー準拠に必要です。

DeleteAccountButton.tsx
import { useAuthon } from "@authon/react-native";
import { Alert } from "react-native";

function DeleteAccountButton() {
  const { deleteAccount, signOut } = useAuthon();

  const handleDelete = () => {
    Alert.alert(
      "Delete Account",
      "This will permanently delete your account and all data. This cannot be undone.",
      [
        { text: "Cancel", style: "cancel" },
        {
          text: "Delete",
          style: "destructive",
          onPress: async () => {
            try {
              await deleteAccount();
              // User is now signed out and account is deleted.
            } catch (err: any) {
              Alert.alert("Error", err.message ?? "Failed to delete account");
            }
          },
        },
      ],
    );
  };

  return <Button title="Delete Account" onPress={handleDelete} color="red" />;
}
重要な注意点
  • OAuth プロバイダーには `myapp://...` を直接登録しないでください。redirect URI は常に `{apiUrl}/v1/auth/oauth/redirect` を使います。
  • `returnTo` にはアプリ復帰用のブリッジ URL を指定します。HTTPS である必要があり、origin は `ALLOWED_REDIRECT_ORIGINS` に含めてください。
  • ブリッジページでは JS リダイレクトではなく HTTP 3xx サーバーリダイレクトを使ってください。Android Custom Tab は中間ページの JS を確実に実行できず、ブラウザが開いたままになることがあります。
  • リダイレクトチェーン: Provider → api.authon.dev → HTTPS ブリッジ(302) → myapp://oauth-callback。すべての遷移は HTTP リダイレクトであるべきです。
  • `completeOAuth()` は poll レスポンスが `status=error` の場合即座に reject します。必ず try/catch で囲んでください。エラーを無視するとローディング状態が無限に続きます。
  • サインアウト時は必ず `signOut()` を呼び出してください。アプリのローカル状態だけクリアして Authon セッションを残すと、プロバイダー切り替え時に問題が起きます。
  • モバイルアプリが独自バックエンドセッションも持つ場合は、`completeOAuth()` の直後に `getToken()` をバックエンドへ渡して独自セッションを発行してください。
  • ブリッジ/コールバックルートは locale 非依存にしてください(例: `/authon/mobile-callback`、`/en/authon/mobile-callback` ではなく)。locale ルーティング問題を防ぎます。
Authon — ユニバーサル認証プラットフォーム