13
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React Navigation v5 が推奨する認証フローの実装

Posted at

はじめに

前回の記事に続いて、 React Navigation v5 に関する記事です。

Version 5.x では、公式ドキュメントで推奨の認証フローが紹介されています。
今回は、その認証フローの実装方法に関して記事にまとめました。

下準備

認証フローを実装するにあたり、アプリ全体で状態を管理する必要があります。
この記事では、 Context API を利用して実装します。

型定義

利用する型を定義します。
必要に応じて、Errorなどの状態・処理も追加してください。

type State =
  | { status: 'Unauthenticated'; token: string }
  | { status: 'Loading';}
  | { status: 'Authenticated'; token: string };

type Action =
  | { type: 'START_LOGIN' }
  | { type: 'COMPLETE_LOGIN'; token: string }
  | { type: 'COMPLETE_LOGOUT' };

type Dispatch = (action: Action) => void

Reducer

各アクションにおける取りうる状態を定義します。

const authReducer = (prevState: State, action: Action): State => {
  switch (action.type) {
    case 'START_LOGIN':
      return {
        ...prevState,
        status: 'Loading',
      };
    case 'COMPLETE_LOGIN':
      return {
        ...prevState,
        status: 'Authenticated',
        token: action.token,
      };
    case 'COMPLETE_LOGOUT':
      return {
        ...prevState,
        status: 'Unauthenticated',
        token: undefined,
      };
  }
};

コンテクスト

アプリの状態、ディスパッチャーそれぞれのコンテクストを定義します。

const AuthStateContext = createContext<State>({
  status: 'Unauthenticated',
  token: undefined,
});

const AuthDispatchContext = createContext<Dispatch | undefined>(undefined);

自作 Hooks

慣習に従い、useXXXXの形で Hooks を定義します。

export const useAuthState = () => {
  const context = React.useContext(AuthStateContext);
  return context;
};

export const useAuthDispatch = () => {
  const context = React.useContext(AuthDispatchContext);
  return context;
};

プロバイダー

アプリ全体で状態を扱えるように、プロバイダーを定義し、値を伝播させます。

interface Props {
  readonly children: React.ReactNode;
}

const AuthProvider: React.FunctionComponent<Props> = ({ children }) => {
  const [state, dispatch] = useReducer(authReducer, {
    status: 'Unauthenticated',
    token: undefined,
  });

  return (
    <AuthStateContext.Provider value={state}>
      <AuthDispatchContext.Provider value={dispatch}>
        {children}
      </AuthDispatchContext.Provider>
    </AuthStateContext.Provider>
  );
};

定義したプロバイダーをアプリに適用します。

const App: React.FunctionComponent = () => {
  return (
    <AuthProvider>
      <NavigationContainer>
        ...
      </NavigationContainer>
    </AuthProvider>
  );
};

ここまでで状態管理をするためのプロバイダーが準備できました。
子コンポーネントでuseAuthStateuseAuthDispatchが扱えるようになりました。

これで準備は完了です。

認証状態による表示画面の出し分け

認証処理を実装します。

const Stack = createStackNavigator();
const StackNavigator = () => {
  const state = useAuthState();

  if (state.status === 'Loading') {
    return <LoadingScreen />;
  }

  return (
    <Stack.Navigator
      headerMode="none"
      screenOptions={{ animationEnabled: false }}
    >
      {state.status === 'Authenticated' ? (
        <Stack.Screen name="Home" component={HomeScreen} />
      ) : (
        <Stack.Screen name="SignIn" component={SignInScreen} />
      )}
    </Stack.Navigator>
  );
};

useAuthSateでアプリの状態を取得することができます。
上記の例では、状態を元に処理を分けています。

状態がLoadingの場合(データの取得中など)には、LoadingScreenを表示します。
それ以外の状態の時は、Stack.Navigatorを表示します。

ナビゲーターを表示する際には、状態がAutheticated(認証済)の場合はHomeScreenを、それ以外の場合はSignInScreenを表示するように処理しています。

初期状態はUnauthenticatedからスタートします。
そのため、SignInScreenに遷移します。

では、それぞれの画面はどのようになっているのか、見ていきます。

各画面の実装

ログイン画面

const SignInScreen: React.FunctionComponent = () => {
  const dispatch = useAuthDispatch();
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button
        title="SignIn"
        onPress={() =>
          dispatch({ type: 'COMPLETE_LOGIN', token: 'dummy-token' })
        }
      />
    </View>
  );
};

ここではログインボタンだけを実装しています。
ログインボタンを押した際、アプリにCOMPLETE_LOGINの状態をディスパッチします。
※本来は認証チェックをすべてパスした場合に、状態をディスパッチすべきですが今回は省略します。

これにより、状態はAuthenticatedへと変化します。

新しい状態に変化すると、stateが更新されて、再度レンダリングが行われます。
statusAuthenticatedになっているため、今度はHomeScreenにナビゲーションされます。

ホーム画面

const HomeScreen: React.FunctionComponent = () => {
  const dispatch = useAuthDispatch();
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button
        title="SignOut"
        onPress={() => dispatch({ type: 'COMPLETE_LOGOUT' })}
      />
    </View>
  );
};

ここではログアウトボタンだけを実装しています。
ボタンを押下すると、ログアウトした状態(COMPLETE_LOGOUT)がディスパッチされます。

これにより、再びログインページへと遷移します。

おわりに

いかがだったでしょうか。
今回は複雑な処理は省略しましたが、状態でStack.Screenを制御することで許可されていないスクリーンに遷移することを防ぐことができます。

また、サーバーとの通信中はローディング画面を表示する、など柔軟に表示内容を変更することができます。

React Navigation で認証ありのアプリを作成する場合、この認証処理も選択肢としてはありかもしれません。

もし記事に関して何かありましたら、ぜひコメントをよろしくお願いします。

13
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?