📚

AWS Cognito SDKをNext.js(TypeScript)に組み込んで使ってみる

2022/03/06に公開

概要

AWS Cognito の SDK を使用して、Next.js に組み込んでログイン認証機能を作成してみます。

なお、SDK には種類があり、aws-sdkamazon-cognito-identity-jsの2つがあります。サーバサイドで実装する場合は、aws-sdkを用いるのが良いと思います。クライアントサイドで実装する場合は、amazon-cognito-identity-jsが利用できます。

本記事では、クライアントサイドで実装する場合の、amazon-cognito-identity-jsを利用する方法について記載します。

前提とする構成

  • Next.js
  • TypeScript
  • AWS Cognito
    • Client Secret は使用しない
  • amazon-cognito-identity-js

手順

Congito SDK のインストール

次のコマンドを実行し、Cognito の SDK を Next.js のプロジェクトにインストール(追加)します。

shell
yarn add amazon-cognito-identity-js

SDK を使用するページでの import

SDK を使用するページで、次の import を行います。

import * as AmazonCognitoIdentity from "amazon-cognito-identity-js";

以上で、Congito User Pool に関する処理を SDK で呼び出せるようになります。

実装例

SDK を用いて、会員登録画面、E-mail 認証画面、ログイン画面を実装した例になります。

前提事項

  • 会員登録時に取得する属性情報は、E-mail とパスワードのみ
  • E-mail に関しては有効性確認を行う
  • ログインは E-mail とパスワードで行える

実装構成

.
├ components
│  └ ConfirmContext.tsx :E-mail認証用に、画面間でE-mailを受け渡す
├ pages
│  ├ _app.tsx :画面間でのE-mail受渡用のContextProvide呼出
│  ├ signup.tsx :登録画面
│  ├ confirm.tsx :E-mail認証画面
│  └ login.tsx :ログイン画面

実装例

components/ConfirmContext.tsx
import { createContext } from "react";

// 画面間でのE-mail値受け渡し用のContextのインターフェースを定義
export interface ConfirmContextType {
  email: string;
  setEmail: (email: string) => void;
}

// Context初期化
export const ConfirmContext = createContext<ConfirmContextType>({
  email: "",
  setEmail: () => {},
});
pages/_app.tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { ConfirmContext } from "../components/ConfirmContext";
import { useState } from "react";

function MyApp({ Component, pageProps }: AppProps) {
  // E-mail有効性確認用のstateを定義し、画面間で受け渡せるようstateをContextProvideに渡す
  const [email, setEmail] = useState<string>("");
  return (
    <ConfirmContext.Provider value={{ email, setEmail }}>
      <Component {...pageProps} />
    </ConfirmContext.Provider>
  );
}

export default MyApp;
pages/signup.tsx
import * as AmazonCognitoIdentity from "amazon-cognito-identity-js";
import { useRouter } from "next/router";
import { useContext, useRef } from "react";
import { ConfirmContext } from "../components/ConfirmContext";

const Signup = () => {
  // Contextから画面間でのE-mail値受け渡し用state関数を取得
  const { setEmail } = useContext(ConfirmContext);

  // 画面内で使用するrefとrouterを定義
  const refEmail = useRef<HTMLInputElement>(null);
  const refPassword = useRef<HTMLInputElement>(null);
  const router = useRouter();

  // Cognito User Poolへの接続情報を設定(実際には環境変数などから取得した方が良い)
  const poolData = {
    UserPoolId: "<UserPoolId>",
    ClientId: "<ClientId>",
  };

  // Cognito User Poolへ接続
  const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

  // SignUpボタン押下時処理
  const submitHandler = (e: { preventDefault: () => void }) => {
    // E-mail未入力エラー時のハンドリングを実装する
    if (!refEmail.current?.value) {
      console.log("E-mail is Empty");
      return;
    }
    // パスワード未入力エラー時のハンドリングを実装する
    if (!refPassword.current?.value) {
      console.log("Password is Empty");
      return;
    }

    // 入力されたE-mailをCognitoに渡す属性情報リストに追加する
    let attributeList = [];
    let attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute({
      Name: "email",
      Value: refEmail.current.value,
    });
    attributeList.push(attributeEmail);

    // Cognitoのユーザ登録処理を呼び出す
    // username=入力されたE-mail、password=入力されたパスワード、userAttributes=[入力されたE-mail]
    userPool.signUp(
      refEmail.current.value,
      refPassword.current.value,
      attributeList,
      attributeList,
      (err, result) => {
        // 登録がエラーとなった場合の処理を実装
        if (err) {
          alert(JSON.stringify(err));
          return;
        }
        // 登録が成功した場合の処理を実装(E-mailは未認証)
        if (result) {
          let cognitoUser = result.user;
          console.log(cognitoUser);

          // E-mailが再取得できない場合の処理を実装(基本的に発生しない)
          if (!refEmail.current?.value) {
            alert("missing email");
            return;
          }

          // Contextから取得した画面間受け渡し用の関数にE-mailを渡す(E-mail認証画面で再度使用するため)
          setEmail(refEmail.current.value);

          // E-mail認証画面へ遷移する
          router.push("/confirm");
        } else {
          alert("missing result");
          return;
        }
      }
    );
  };

  return (
    <>
      <h1>This is Signup page</h1>
      <div>
        E-mail:
        <input type="text" ref={refEmail} />
        Password:
        <input type="text" ref={refPassword} />
        <button onClick={submitHandler}>SignUp</button>
      </div>
    </>
  );
};

export default Signup;
pages/confirm.tsx
import * as AmazonCognitoIdentity from "amazon-cognito-identity-js";
import { useContext, useRef } from "react";
import { ConfirmContext } from "../components/ConfirmContext";

const Confirm = () => {
  // 画面間での受け渡し用のContextからE-mailを取り出す
  const { email } = useContext(ConfirmContext);

  // 画面内で使用するrefを定義
  const refCode = useRef<HTMLInputElement>(null);

  // Cognito User Poolへの接続情報を設定(実際には環境変数などから取得した方が良い)
  const poolData = {
    UserPoolId: "<UserPoolId>",
    ClientId: "<ClientId>",
  };

  // Cognito User Poolへ接続
  const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

  // Confirmボタン押下時処理
  const submitHandler = (e: { preventDefault: () => void }) => {
    // 認証コード未入力エラー時のハンドリングを実装する
    if (!refCode.current?.value) {
      console.log("Code is Empty");
      return;
    }
    // E-mail認証の対象とするCognito User Poolとユーザを設定する
    let userData = {
      Username: email,
      Pool: userPool,
    };
    let cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);

    // E-mail認証処理を呼び出し
    cognitoUser.confirmRegistration(
      refCode.current.value,
      true,
      function (err, result) {
        // E-mail認証がエラーとなった場合の処理を実装
        if (err) {
          alert(JSON.stringify(err));
          return;
        }
        // E-mail認証が成功した場合の処理を実装
        console.log(result);
      }
    );
  };

  return (
    <>
      <h1>This is Confirm page</h1>
      <div>
        Code:
        <input type="text" ref={refCode} />
        <button onClick={submitHandler}>Confirm</button>
      </div>
    </>
  );
};

export default Confirm;
pages/login.tsx
import * as AmazonCognitoIdentity from "amazon-cognito-identity-js";
import { useRef } from "react";

const Login = () => {
  // 画面内で使用するrefを定義
  const refEmail = useRef<HTMLInputElement>(null);
  const refPassword = useRef<HTMLInputElement>(null);

  // Cognito User Poolへの接続情報を設定(実際には環境変数などから取得した方が良い)
  const poolData = {
    UserPoolId: "<UserPoolId>",
    ClientId: "<ClientId>",
  };
  const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

  // Loginボタン押下時の処理を実装
  const submitHandler = (e: { preventDefault: () => void }) => {
    // E-mail未入力エラー時のハンドリングを実装する
    if (!refEmail.current?.value) {
      console.log("E-mail is Empty");
      return;
    }
    // パスワード未入力エラー時のハンドリングを実装する
    if (!refPassword.current?.value) {
      console.log("Password is Empty");
      return;
    }

    // ログイン認証の対象とするCognito User Poolとユーザを設定する
    let userData = {
      Username: refEmail.current.value,
      Pool: userPool,
    };
    let cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);

    // 認証用に渡すusernameとpasswordを設定する
    let authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
      {
        Username: refEmail.current.value,
        Password: refPassword.current.value,
      }
    );

    // ログイン認証を実行する
    cognitoUser.authenticateUser(authenticationDetails, {
      onSuccess: function (result) {
        // ログイン成功時の処理を実装する
        console.log("Login Success");
        console.log(result);
        console.log(
          "Access Token(jwtToken)=" + result.getAccessToken().getJwtToken()
        );
        console.log("ID Token(jwtToken)=" + result.getIdToken().getJwtToken());
        console.log("Refresh Token=" + result.getRefreshToken().getToken());
        console.log(
          "username(congitoの一意ID)=" +
            result.getAccessToken().decodePayload().username
        );
      },
      onFailure: function (err) {
        // ログイン失敗時の処理を実装する
        alert(JSON.stringify(err));
      },
    });
  };

  return (
    <>
      <h1>This is Login page</h1>
      <div>
        E-mail:
        <input type="text" ref={refEmail} />
        Password:
        <input type="text" ref={refPassword} />
        <button onClick={submitHandler}>Login</button>
      </div>
    </>
  );
};

export default Login;

<UserPoolId><ClientId>には Cognito で事前に取得した User Pool ID とアプリケーションの ClientID を設定して下さい。

備考

上記は Client Secret を使用しない例で、クライアント処理で記載しています。
Client Secret を使する場合は、aws-sdkのほうを利用する必要があります。

参考:Congito SDK 公式

パスワード忘れ処理など、その他の処理も基本的に同じ流れで実装できます。

https://github.com/aws-amplify/amplify-js/tree/main/packages/amazon-cognito-identity-js

Discussion