メインコンテンツにスキップ
バージョン: 6.x

認証フロー

ほとんどのアプリでは、ユーザーに関連付けられたデータやその他のプライベートコンテンツにアクセスするために、何らかの方法でユーザーを認証する必要があります。 通常、フローは次のようになります。

  • ユーザーがアプリを開きます。
  • アプリは、暗号化された永続ストレージ(たとえば、SecureStore)から認証状態を読み込みます。
  • 状態が読み込まれると、有効な認証状態が読み込まれたかどうかによって、ユーザーには認証画面またはメインアプリが表示されます。
  • ユーザーがサインアウトすると、認証状態をクリアし、認証画面に戻します。
注記

通常は複数あるため、「認証画面」と呼んでいます。 ユーザー名とパスワードのフィールドを含むメイン画面、「パスワードを忘れた場合」の画面、サインアップ用の別の画面セットがある場合があります。

必要なもの

これは、認証フローに求める動作です。ユーザーがサインインすると、認証フローの状態を破棄し、認証に関連するすべての画面をアンマウントします。また、ハードウェアの戻るボタンを押すと、認証フローに戻ることができないことが期待されます。

仕組み

条件に基づいて異なる画面を定義できます。 たとえば、ユーザーがサインインしている場合は、HomeProfileSettingsなどを定義できます。 ユーザーがサインインしていない場合は、SignIn画面とSignUp画面を定義できます。

例えば

isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</>
) : (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</>
);

このように画面を定義すると、isSignedIntrueの場合、React NavigationはHomeProfileSettings画面のみを認識し、falseの場合は、SignIn画面とSignUp画面を認識します。 これにより、ユーザーがサインインしていないときにHomeProfileSettings画面に移動したり、ユーザーがサインインしているときにSignIn画面とSignUp画面に移動したりすることができなくなります。

このパターンは、React Routerなどの他のルーティングライブラリで長い間使用されており、「保護されたルート」として一般的に知られています。 ここでは、ユーザーがサインインする必要がある画面は「保護」されており、ユーザーがサインインしていない場合は他の方法で移動することはできません。

isSignedIn変数の値が変更されると、魔法が起こります。 最初にisSignedInfalseだとしましょう。 これは、SignIn画面またはSignUp画面が表示されていることを意味します。 ユーザーがサインインした後、isSignedInの値はtrueに変更されます。 React Navigationは、SignIn画面とSignUp画面が定義されなくなったことを認識し、それらを削除します。 その後、isSignedIntrueの場合に定義された最初の画面であるため、自動的にHome画面が表示されます。

この例ではスタックナビゲーターを示していますが、同じアプローチをどのナビゲーターでも使用できます。

変数に基づいて異なる画面を条件付きで定義することにより、正しい画面が表示されることを確認するための追加のロジックを必要としない簡単な方法で認証フローを実装できます。

条件付きで画面をレンダリングする場合は、手動でナビゲートしないでください

このような設定を使用する場合、navigation.navigate('Home')またはその他のメソッドを呼び出してHome画面に**手動で移動しない**ことが重要です。 isSignedInが変更されると、**React Navigationは自動的に正しい画面に移動します**。isSignedIntrueになるとHome画面に、isSignedInfalseになるとSignIn画面に移動します。 手動でナビゲートしようとするとエラーが発生します。

画面を定義する

ナビゲーターでは、適切な画面を条件付きで定義できます。 この場合、3つの画面があるとしましょう。

  • SplashScreen - トークンを復元しているときにスプラッシュ画面または読み込み画面を表示します。
  • SignInScreen - ユーザーがまだサインインしていない場合に表示する画面です(トークンが見つかりませんでした)。
  • HomeScreen - ユーザーがすでにサインインしている場合に表示する画面です。

そのため、ナビゲーターは次のようになります。

if (state.isLoading) {
// We haven't finished checking for the token yet
return <SplashScreen />;
}

return (
<Stack.Navigator>
{state.userToken == null ? (
// No token found, user isn't signed in
<Stack.Screen
name="SignIn"
component={SignInScreen}
options={{
title: 'Sign in',
// When logging out, a pop animation feels intuitive
// You can remove this if you want the default 'push' animation
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
}}
/>
) : (
// User is signed in
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
);

上記のコードスニペットでは、isLoadingは、まだトークンがあるかどうかを確認していることを意味します。 これは通常、SecureStoreにトークンがあるかどうかを確認し、トークンを検証することで実行できます。 トークンを取得し、それが有効な場合は、userTokenを設定する必要があります。 また、サインアウト時に異なるアニメーションを持つために、isSignoutと呼ばれる別の状態もあります。

主な注意点は、これらの状態変数に基づいて画面を条件付きで定義していることです。

  • SignIn画面は、userTokennull(ユーザーがサインインしていない)の場合にのみ定義されます。
  • Home画面は、userTokenがnull以外(ユーザーがサインインしている)の場合にのみ定義されます。

ここでは、ケースごとに1つの画面を条件付きで定義しています.しかし、複数の画面を定義することもできます.たとえば、ユーザーがサインインしていない場合は、パスワードのリセット、サインアップなどの画面も定義する必要があります。 同様に、サインイン後にアクセスできる画面には、複数の画面がある可能性があります。 React.Fragmentを使用して複数の画面を定義できます。

state.userToken == null ? (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
<Stack.Screen name="ResetPassword" component={ResetPassword} />
</>
) : (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</>
);
ヒント

ログイン関連の画面と残りの画面の両方が2つの異なるスタックナビゲーターにある場合は、2つの異なるナビゲーターを使用する代わりに、単一のスタックナビゲーターを使用し、その中に条件を配置することをお勧めします。 これにより、ログイン/ログアウト中に適切なトランジションアニメーションを実行できます。

トークンを復元するためのロジックを実装する

注記

以下は、アプリで認証ロジックを実装する方法の例です。 そのまま従う必要はありません。

前のコードスニペットから、3つの状態変数が必要であることがわかります。

  • isLoading - SecureStoreに保存されているトークンがすでにあるかどうかを確認しようとしているときに、これをtrueに設定します。
  • isSignout - ユーザーがサインアウトしているときにこれをtrueに設定し、それ以外の場合はfalseに設定します。
  • userToken - ユーザーのトークン。 null以外の場合、ユーザーはログインしていると想定し、それ以外の場合はログインしていないと想定します。

そのため、次のことを行う必要があります。

  • トークンの復元、サインイン、サインアウトのロジックを追加します。
  • サインインとサインアウトのメソッドを他のコンポーネントに公開します。

このガイドでは、React.useReducerReact.useContextを使用します。 ただし、ReduxやMobxなどの状態管理ライブラリを使用している場合は、代わりにこの機能に使用できます。 実際、大規模なアプリでは、グローバル状態管理ライブラリは認証トークンを格納するのに適しています。 同じアプローチを状態管理ライブラリに適用できます。

まず、必要なメソッドを公開できる認証のコンテキストを作成する必要があります。

import * as React from 'react';

const AuthContext = React.createContext();

そのため、コンポーネントは次のようになります。

import * as React from 'react';
import * as SecureStore from 'expo-secure-store';

export default function App({ navigation }) {
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...prevState,
userToken: action.token,
isLoading: false,
};
case 'SIGN_IN':
return {
...prevState,
isSignout: false,
userToken: action.token,
};
case 'SIGN_OUT':
return {
...prevState,
isSignout: true,
userToken: null,
};
}
},
{
isLoading: true,
isSignout: false,
userToken: null,
}
);

React.useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let userToken;

try {
userToken = await SecureStore.getItemAsync('userToken');
} catch (e) {
// Restoring token failed
}

// After restoring token, we may need to validate it in production apps

// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
dispatch({ type: 'RESTORE_TOKEN', token: userToken });
};

bootstrapAsync();
}, []);

const authContext = React.useMemo(
() => ({
signIn: async (data) => {
// In a production app, we need to send some data (usually username, password) to server and get a token
// We will also need to handle errors if sign in failed
// After getting token, we need to persist the token using `SecureStore`
// In the example, we'll use a dummy token

dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
signOut: () => dispatch({ type: 'SIGN_OUT' }),
signUp: async (data) => {
// In a production app, we need to send user data to server and get a token
// We will also need to handle errors if sign up failed
// After getting token, we need to persist the token using `SecureStore`
// In the example, we'll use a dummy token

dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
}),
[]
);

return (
<AuthContext.Provider value={authContext}>
<Stack.Navigator>
{state.userToken == null ? (
<Stack.Screen name="SignIn" component={SignInScreen} />
) : (
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
</AuthContext.Provider>
);
}

他のコンポーネントに入力する

認証画面のテキスト入力とボタンの実装方法については説明しません。これはナビゲーションの範囲外です。 プレースホルダーコンテンツを入力するだけです。

function SignInScreen() {
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');

const { signIn } = React.useContext(AuthContext);

return (
<View>
<TextInput
placeholder="Username"
value={username}
onChangeText={setUsername}
/>
<TextInput
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<Button title="Sign in" onPress={() => signIn({ username, password })} />
</View>
);
}

認証状態の変更時に共有画面を削除する

次の例を考えてみましょう。

isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Help" component={HelpScreen} />
</>
) : (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
<Stack.Screen name="Help" component={HelpScreen} />
</>
);

ここでは、サインイン状態に応じてのみ表示されるSignInHomeなどの特定の画面があります。 ただし、どちらの場合でも表示できるHelp画面もあります。 これはまた、ユーザーがHelp画面にいるときにサインイン状態が変更された場合、Help画面にとどまることを意味します。

これは問題になる可能性があります。ユーザーをHelp画面に留めておくのではなく、SignIn画面またはHome画面に移動することをお勧めします。 これを実現するには、navigationKey propを使用できます。 navigationKeyが変更されると、React Navigationはすべての画面を削除します。

更新されたコードは以下のようになります。

<>
{isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</>
) : (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</>
)}
<Stack.Screen
navigationKey={isSignedIn ? 'user' : 'guest'}
name="Help"
component={HelpScreen}
/>
</>

共有画面が多数ある場合は、navigationKeyGroup と共に使用して、グループ内のすべての画面を削除することもできます。 例:

<>
{isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</>
) : (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</>
)}
<Stack.Group navigationKey={isSignedIn ? 'user' : 'guest'}>
<Stack.Screen name="Help" component={HelpScreen} />
<Stack.Screen name="About" component={AboutScreen} />
</Stack.Group>
</>