【Next.js】Next.jsからCognitoを使ってログイン画面を実装する②

前回はCognitoユーザープールと動作確認用のユーザーを作成しました。
今回は認証処理の実装と作成したユーザーを用いて動作確認を実施します。
前回の記事はこちらを参照してください。

目次

Contextとは

認証処理を実装するにあたりReactのContext機能を利用します。

Contextは配下の子コンポーネントにデータを渡すための便利な方法です。
通常ReactではPropsが親コンポーネントから子コンポーネントに情報を伝達します。しかしながら、コンポーネント階層が深くなってくると、情報のバケツリレーが発生して面倒で複雑になります。
Contextを利用することで認証情報など多くのコンポーネントが使用する情報を共有して参照することができます。どのコンポーネントからも利用可能なStateのようなイメージです。

React.create.Context

Contextは値を共有する最上位のコンポーネントで準備します。
createContextでContextオブジェクトを作成します。

const AuthContext = React.createContext(defaultValue);

// defaultValue:コンテキストの既定値

Context.Provider

全ての Context オジェクトには Context.Provider コンポーネントが付属しています。
配下のコンポーネントに対してContextを適用するために使用します。

<AuthContext.Provider value={/* 何らかの値 */}>

useContext

Contextオブジェクトを受取り、valueを返却します。

const value = useContext(AuthContext);

aws-amplifyについて

AWS Amplifyは、モバイルアプリケーションとウェブアプリケーションを構築するためのAWSがOSSで公開する開発プラットフォームです。簡単に言えば、AWSでもFirebaseみたいにバックエンドはお任せでフロントだけ開発できるようにしてくれるフレームワークみたいなやつです。

Next.jsでAmplifyのライブラリを使用するために、aws-amplifyを利用します。

npm install aws-amplify

package.jsonにaws-amplifyが追加されたことを確認します。

aws-amplifyの使用方法は、下記ドキュメントに記載されています。

Amplifyライブラリの設定ファイルを定義します。
src/config/aws.jsを作成し、以下を記述します。

const config = {
    Auth: {
        Cognito: {
            userPoolClientId:
            process.env.NEXT_PUBLIC_COGNITO_USER_POOLS_WEB_CLIENT_ID,
            userPoolId: process.env.NEXT_PUBLIC_USER_POOLS_ID,
        },
    },
};

export default config;

環境変数について

Next.jsでは環境変数を利用することができます。
大まかには.env.localファイルを作成して、環境変数を定義します。NEXT_PUBLIC_ keyのprefixにすることで、ClientでもServer側でも読み込めるようになります。

先ほどaws.jsに以下環境変数を読み込むように記載しました。
こちらを.env.localに定義すればよいということです。

  • NEXT_PUBLIC_COGNITO_USER_POOLS_WEB_CLIENT_ID
  • NEXT_PUBLIC_USER_POOLS_ID
# AWS
## Cognito
NEXT_PUBLIC_COGNITO_USER_POOLS_WEB_CLIENT_ID=xxx
NEXT_PUBLIC_USER_POOLS_ID=xxx

NEXT_PUBLIC_COGNITO_USER_POOLS_WEB_CLIENT_IDはマネジメントコンソールの青枠部分を設定します。

NEXT_PUBLIC_USER_POOLS_IDは以下で確認します。

エラーメッセージ用の設定ファイルを作成する

cognito認証時、パスワードが異なる場合などに表示するエラーメッセージを定義します。
src/config/message.jsを作成します。

/**
 * 画面表示メッセージ一覧
 */
const message = {
    M1001: "メールアドレスまたはパスワードが異なります。",
};

export default message;

認証処理を作成する

Amplifyライブラリと環境変数の準備ができたので、いよいよ認証処理を実装します。
認証情報としてログインメソッドとメールアドレスステートを保持する Context を作成します。
ログインにはaws-amplify/authsiginInメソッドを利用します。
src/contexts/authContext.jsx

import {Amplify} from "aws-amplify";
import config from "@/config/aws";
import React, {useContext, useState} from "react";
import {signIn} from "aws-amplify/auth";
import message from "@/config/message";

Amplify.configure(config, {ssr: true});

const AuthContext = React.createContext(undefined);

export const useAuth = () => {
    return useContext(AuthContext);
}

const AuthProvider = ({children}) => {
    const [user, setUser] = useState(undefined);

    // ログイン処理
    const login = async (email, password) => {
        try {
            await signIn({username: email, password: password,});
            setUser({email: email});
            return {isSuccessed: true, errorMessage: undefined, urlTo: "/"};
        } catch (error) {
            return {isSuccessed: false, errorMessage: message.M1001, urlTo: undefined,};
        }
    }

    return (
        <AuthContext.Provider value={{login, user}}>
            {children}
        </AuthContext.Provider>
    );
};
export default AuthProvider;

src/pages/__app.jsxを以下のように修正し、AuthContextを利用できる状態にします。

import "@/styles/globals.css";
import AuthContext from "@/contexts/authContext";

export default function App({Component, pageProps}) {
    return (
        <AuthContext>
            <Component {...pageProps} />
        </AuthContext>
    );
}

src/pages/auth.jsxを以下のように修正します。
AuthContextのログインメソッドを利用し、認証処理を実行します。仮に認証に失敗した場合は予め定義しておいたエラーメッセージを表示します。

import {useState} from "react";
import {ButtonBgEmerald} from "@/components/elements/button";
import {InputTextForm, InputPassForm} from "@/components/elements/form";
import {NoAuthLayout} from "@/components/templates/layout";
import {NoAuthHeading1} from "@/components/elements/head";
import yup from "@/config/yup.jp";
import {useRouter} from "next/navigation";
import {passwordRule} from "@/config/yup.jp";
import {useForm} from "react-hook-form";
import {yupResolver} from "@hookform/resolvers/yup";
import {useAuth} from "@/contexts/authContext";

/* バリデーション定義 */
const schema = yup.object({
    email: yup.string().label("メールアドレス").required().email(),
    password: yup
        .string()
        .label("パスワード")
        .required()
        .min(8)
        .max(32)
        .matches(passwordRule),
});

/**
 * ログインフォーム
 * @returns
 */
export default function Auth() {
    const router = useRouter();
    const {login} = useAuth();
    const [errorMessage, setErrorMessage] = useState("");
    const {
        register,
        handleSubmit,
        formState: {errors},
    } = useForm({
        defaultValues: {},
        resolver: yupResolver(schema),
    });

    /* ログイン処理 */
    const onSubmit = async (data) => {
        const result = await login(data["email"], data["password"]);
        if (result.isSuccessed) {
            router.push(result.urlTo);
        } else {
            setErrorMessage(result.errorMessage);
        }
    };

    return (
        <NoAuthLayout>
            <form onSubmit={handleSubmit(onSubmit)} className="w-full max-w-md mt-auto">
                <NoAuthHeading1 title="クラウド労務"/>
                <InputTextForm
                    title="メールアドレス"
                    name="email"
                    errorMessage={errors.email?.message}
                    register={register}
                />
                <InputPassForm
                    title="パスワード"
                    type="password"
                    name="password"
                    errorMessage={errors.password?.message}
                    register={register}
                />
                <ButtonBgEmerald title="ログイン" type="submit"/>
                <div className="mt-2">
                    <span className="text-red-500 text-sm">{errorMessage}</span>
                </div>
                <div className="mt-6 text-center ">
                    <a className="text-sm text-emerald-500 hover:text-emerald-400" href="#">
                        パスワードをお忘れの方
                    </a>
                </div>
            </form>
        </NoAuthLayout>
    );
}

バリデーションエラーとならないように存在しないユーザー情報を入力し、定義したエラーメッセージが表示されることを確認します。

最後に先ほど前回の記事で作成した動作確認用のユーザー情報を入力し、ログイン処理が成功することを確認します。
src/pages/index.jsxが表示されるはずです。

コードは、以下のGitHubリポジトリで確認できます。

よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

目次