SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

ExpoとEASで始める快適モバイルアプリ開発

Expo Routerを使って、手続き的ナビゲーションと認証フローを構築しよう

ExpoとEASで始める快適モバイルアプリ開発 第7回

  • X ポスト
  • このエントリーをはてなブックマークに追加

認証フローの実装例

 多くのアプリケーションでは、ユーザーのログイン状態に応じて表示する画面を切り替える必要があります。Expo Routerでは、こういった認証フローを効率的に実装するための仕組みが提供されています。

 認証フローの基本的なアプローチは以下の通りです。

  1. ReactのコンテキストAPIでアプリ全体の認証状態を管理
  2. ルートレイアウトで認証状態に応じた画面制御
  3. キュアストレージによる認証情報の永続化
  4. カスタムフックを通じたコンポーネント間での状態共有

 詳細なベストプラクティスについては、公式ドキュメントでも解説されています。

図1:認証フローに関するドキュメント
図1:認証フローに関するドキュメント

 今回は雰囲気をつかんでもらえるよう、簡易版の実装を解説します。まずは、認証フローに関わる主要なファイルとその関係を整理しておきましょう(図2)。

図2:認証フローの構成要素と関係図
図2:認証フローの構成要素と関係図

 中心となるのはcontexts/AuthContext.tsxで、アプリ全体の認証状態を管理します。ここには SessionProvider(コンテキストプロバイダー)、useSession(カスタムフック)、signIn/signOut(認証処理メソッド)が含まれます。

 app/_layout.tsx ではuseSessionを使って認証状態を取得し、ログイン済み・未ログインに応じた画面を条件分岐で表示します。app/signin.tsx はログイン画面で、signInメソッドを呼び出して認証処理を実行します。また、expo-secure-storeが認証トークンの永続化を担当し、アプリ起動時の状態復元を可能にします。

認証コンテキストの設定

 まずは、ReactのコンテキストAPIを使用してアプリ全体で認証状態を共有できるようにします(リスト3)。

[リスト3]contexts/AuthContext.tsx
import React, { createContext, useContext, useEffect, useState } from 'react';
import * as SecureStore from 'expo-secure-store';

// 認証状態を管理するコンテキストを作成
const AuthContext = createContext(null);

// 認証状態にアクセスするためのカスタムフック
export function useSession() { // (3)
  const value = useContext(AuthContext);
  if (!value) {
    throw new Error('useSession must be wrapped in a <SessionProvider />');
  }
  return value;
}

// 認証コンテキストを提供するプロバイダーコンポーネント
export function SessionProvider({ children }) {
  // 認証セッション(トークン)を保持するstate
  const [session, setSession] = useState(null);
  // 初期化中かどうかを示すローディング状態
  const [isLoading, setIsLoading] = useState(true);

  // アプリ起動時に保存済みのセッション情報を復元
  useEffect(() => {
    async function loadSession() {
      try {
        // (2) セキュアストレージから認証トークンを取得
        const token = await SecureStore.getItemAsync('authToken');
        setSession(token);
      } finally {
        // 成功・失敗に関わらずローディング状態を終了
        setIsLoading(false);
      }
    }
    loadSession();
  }, []);

  // コンテキストに渡すメソッドと状態をまとめたオブジェクト
  const value = {
    // ログイン処理:トークンをセキュアストレージに保存し、セッション状態を更新
    signIn: async (token) => {
      await SecureStore.setItemAsync('authToken', token); // (1)
      setSession(token);
    },
    // ログアウト処理:トークンを削除し、セッション状態をクリア
    signOut: async () => {
      // 本来はここでログアウトのための通信処理を行う
      await SecureStore.deleteItemAsync('authToken');
      setSession(null);
    },
    session,   // 現在のセッション状態
    isLoading, // ローディング状態
  };

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

 (1)のsignInメソッドはログイン処理が成功したときに呼び出され、認証トークンの永続化を行います。今回はセキュアなデータ保存に優れたexpo-secure-storeを使用しました。ここで保存したデータは、(2)の処理を通してメモリに読み込まれ、(3)のカスタムフックを通じてコンポーネントから読み出されます。

ルートレイアウトでの認証制御

 リスト3で設定した認証状態に応じた画面制御は、ルートレイアウト(app/_layout.tsx)で行います(リスト4)。

[リスト4]app/_layout.tsx
import { Stack } from 'expo-router';
import { SessionProvider, useSession } from '../contexts/AuthContext';
import { Text, View } from 'react-native';

function RootLayoutNav() {
  const { session, isLoading } = useSession(); // (1)

  if (isLoading) {
    return <Text>読み込み中...</Text>;
  }

  return (
    <Stack>
      {session ? ( // (2)
        // ログイン済み
        <>
          <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
          <Stack.Screen name="settings" options={{ title: '設定' }} />
        </>
      ) : (
        // 未ログイン
        <Stack.Screen name="signin" options={{ title: 'サインイン' }} />
      )}
    </Stack>
  );
}

export default function RootLayout() {
  return (
    <SessionProvider>
      <RootLayoutNav />
    </SessionProvider>
  );
}

 最上位のコンポーネントであるapp/_layout.tsxでは、(1)でコンテキストから認証状態を取得し、(2)の条件分岐でログイン済みかどうかに応じて表示する画面を切り替えています。認証状態の明示的な更新を行いたい場合は、useSessionAuthContextの実装を拡張して、任意のタイミングでチェックすることで実現できます。

サインイン画面の実装

 次に、サインイン画面では、認証コンテキストのsignIn関数を使用してログイン処理を行います(リスト5)。

[リスト5]app/signin.tsx
import React from 'react';
import { View, TextInput, Button } from 'react-native';
import { useSession } from '../contexts/AuthContext';
import { useRouter } from 'expo-router';

export default function SignIn() {
  const { signIn } = useSession();
  const router = useRouter();

  const handleSignIn = async () => {
    const token = 'dummy-token'; // (1) 実際はAPIで認証
    await signIn(token);
    router.replace('/'); // メイン画面へ
  };

  return (
    <View style={{ padding: 20, justifyContent: 'center', flex: 1 }}>
      <TextInput placeholder="メールアドレス" />
      <TextInput placeholder="パスワード" secureTextEntry />
      <Button title="ログイン" onPress={handleSignIn} />
    </View>
  );
}

 実際のアプリでは(1)の箇所でログイン処理の通信を行います。前述の通り、 signInの呼び出しが成功すると<SessionProvider>の内部状態が書き換わり、リスト4の条件分岐によってログイン済みの画面が表示されるようになります。

 このように、Expo Routerと認証コンテキストを組み合わせることで、ユーザーの認証状態に応じたスムーズな画面遷移を実現できます。

次のページ
ディープリンク

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
ExpoとEASで始める快適モバイルアプリ開発連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 中川幸哉(ナカガワユキヤ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/22082 2025/08/21 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング