🌀

Vite + React:aws-amplify を使って Cognito でサインインする

2022/05/18に公開2

Vite + React 環境で、既存のCognito を使用したサインインを実装します。
Viteを使用している場合は躓きポイントが2箇所あります。

既存コグニートの設定

今回は既存のコグニートを使用しますので、あらかじめ作成しておきます。
ちなみにコグニートのアプリクライアントの設定は以下のように設定しています。

aws-amplify インストール

aws-amplify をインストールします。
2022年5月17日時点で aws-amplify のバージョンは 4.3.22 でした。

npm install aws-amplify

サインインの最小限のコード

サインインする最小限のコードです。

+ import { Amplify, Auth } from 'aws-amplify'

export const authConfig = {
  // コグニートのリージョン
  region:import.meta.env.VITE_COGNITO_REGION, 
  // コグニートのユーザープールID
  userPoolId:import.meta.env.VITE_COGNITO_POOLID,
  // コグニートのアプリケーションID
  userPoolWebClientId:import.meta.env.VITE_COGNITO_APPCLIENTID,
}

+ Amplify.configure({ Auth: authConfig })

export function LoginTest() {

  const handleLoginClick = async () => {
    try {
+      const cognitoUser = await Auth.signIn('ユーザーID', 'パスワード')
      console.log('認証に成功', cognitoUser)
    } catch (error:any) {
      if ( error.code === 'UserNotFoundException') {
        console.log('cognitoに該当するユーザーIDがない')
      } 
      if ( error.code === 'NotAuthorizedException') {
        console.log('cognitoに該当するユーザーIDはあるがパスワードが一致しない')
      } 
      console.log('それ以外のエラー', error)
    }
  }

  return (
    <button type="button" onClick={handleLoginClick}>ログイン</button>
  )
}

Amplify.configure メソッドの引数に、コグニートのリージョン、ユーザープールID、アプリケーションIDを指定します。
サンプルコードでは .env ファイルにコグニート情報を定義しておき、その値をAmplify.configure メソッドの引数に設定しています。
※ Vite の envファイルについてはコチラ
Vite + React + TypeScript で 環境変数ファイルを作成する

Auth.signInメソッドの引数に、ユーザーID、パスワードを指定してサインインします。
サインインに成功するとコグニートユーザーオブジェクトが返ります。
このコグニートユーザーオブジェクトにトークン情報が入っています。

サインに失敗した場合、
コグニートのユーザープールに該当するユーザーIDがなければ'UserNotFoundException'が返り、該当するユーザーIDはあるがパスワードが一致しない場合は'NotAuthorizedException'が返ります。

Vite でデバッグ実行した際のエラーを解消する

デバッグビルド(デフォルトでnpm run dev)して、デバッグ実行するとエラーが発生しページが表示されません。
エラー内容:Uncaught ReferenceError: global is not defined

これは Vite で作成したアプリだけで発生するようです。
index.htmml の body タグに下記を追加して回避します。

...
  <script>
      window.global = window;
      window.process = {
        env: { DEBUG: undefined },
      };
      var exports = {};
  </script>
</body>

これでデバッグ実行できるようになります。

Vite で productionビルド時のエラーを解消する

次に製品ビルド(デフォルトでnpm run build)するとエラーが発生して、ビルドできません。
エラー内容:
'request' is not exported by __vite-browser-external, imported by node_modules/@aws-sdk/credential-provider-imds/dist/es/remoteProvider/httpRequest.js

こちらも Vite でだけ発生します。
vite.config.ts に下記を追加して回避します。

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import svgr from 'vite-plugin-svgr'

// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    alias: [
+      {find: './runtimeConfig', replacement: './runtimeConfig.browser'},
      {find: '@', replacement: '/src', }
    ]
  },
  plugins: [
    react({
      jsxImportSource: '@emotion/react'
    }),
    svgr()
  ]
})

以上で、Vite で Cognito を使用したサインインができるようになりました。

aws-amplify の Vite 対応は公式サイトに記載があります。
(コメントで教えていただきました。ありがとうございます!)
https://ui.docs.amplify.aws/react/getting-started/installation?platform=react#vite

※ちなみに
公式サイトでは tsconfig.json の compilerOptions に skipLibCheck:true を設定するように記載されています。(が、false のままでも問題なく実行できました。)

tsconfig.json
{
  "compilerOptions": {
  ・・・
    "skipLibCheck": true,

skipLibCheck を true に設定すると *.d.ts ファイルに対する型チェックをスキップすることができます。
skipLibCheck を true にすることで型チェックの精度が下がるデメリットもありますので、適宜設定してください。

サインイン、サインアウト を カスタムフックにする

実装する際はサインインやサインアウトの処理などはカスタムフックにすると思います。
カスタムフックのサンプルです。こんな感じで実装してみました。

useAuthUser.ts
+ import { Amplify, Auth } from 'aws-amplify'

import { useRecoilState, useResetRecoilState } from 'recoil'
import { authUserState } from '@/_states'
import * as types from '@/_types'
import { authConfig } from './authConfig'


+ Amplify.configure({ Auth: authConfig })


/**
 * ユーザー認証に関するフック
 */
export const useAuthUser = () => {
  // state
  const [ authUser, setAuthUser ] = useRecoilState(authUserState)
+  const resetAuthUser = useResetRecoilState(authUserState)

  /**
   * サインイン処理
   */
  const signIn = async (userid: string, password: string): Promise<boolean> => {
    setAuthUser(undefined)
    try {
      const user = await getUser(userid, password)
      if (types.isAuthUser(user)) {
        setAuthUser(user)
        console.log("login success:", user) 
        return true
      }
      console.log("login failed:", user) 
      return false

    } catch (error) {
      return Promise.reject(error)
    }
  }

  /**
   * サインアウト処理
   */
  const signOut = () => {
+    resetAuthUser()
  }

  /**
   * 認証ユーザーの値
   */
  const authUserValue = ():types.AuthUser | undefined => authUser

  /**
   * 認証ユーザの取得
   * @param userid
   * @param password
   * @returns
   */
  const getUser = async (userid: string, password: string): Promise<types.AuthUser | types.AuthFailed> => {
    try {
+      const cognitoUser = await Auth.signIn(userid, password)
      return { 
        name: cognitoUser.username,
        userId: cognitoUser.username,
        jwtToken: cognitoUser.signInUserSession.idToken.jwtToken
       }    
    } catch (error:any) {
      if ( error.code === 'UserNotFoundException') {
        // cognito のユーザーIDが存在しない場合
        return Promise.resolve({ failedCode: 'UserNotFound'})
      } 
      if ( error.code === 'NotAuthorizedException') {
        // cognito にユーザーIDに該当するデータはあるが、パスワードが一致しないとき
        return Promise.resolve({ failedCode: 'NotAuthorized'})
      } 
      return Promise.reject(error)
    }

  }

  return { signIn, signOut, authUserValue }
}

サンプルコード

全体のコードは GitHub に置いています。
ログイン画面 でサインインすると、トップ画面を表示します。
トップ画面は認証ユーザーのみが表示でき、未認証のユーザーがアクセスした場合はログイン画面にリダイレクトします。

状態管理はrecoilを使用し、認証情報はローカルストレージに保存しています。
UIはMaterial-UI + react-hook-form + yup で作ってます。
https://github.com/longbridgeyuk/react18-cognito.git

サンプルコードは現在まで書いた過去記事の内容で実装されています。
参考過去記事:
React: React Router v6 でルーティングする step1
React:React Router v6 で 認証されていないユーザーや権限がないユーザーをリダイレクトする
React: アプリ内のエラーを全部まとめて処理する(react-error-boundary)
Vite+React+TypeScript+EsLintで、Importパスにエイリアスを使うためにハマったこと
Vite+React+TypeScript+EsLintに Reciolを導入して Stateをローカルストレージで永続化する
Vite+React+TypeScript で、UIフレームワークにMUI(v5)を導入する。
React+MUI v5 の 入力フォーム用のライブラリは React Hook Form の 一択
React 今更だけど改めて Validation ライブラリ yup の日本語化
【2022年】 React Hook FormでValidationライブラリはどれにするか?
React 今更だけど改めて Validation ライブラリ yup の日本語化
Vite + React + TypeScript で 環境変数ファイルを作成する

まとめ

今回の Cognito 連携の躓きポイントは、開発用ビルドで躓きポイントを解消させて、一旦安心させてからのまさかの実行時ビルドでまた躓かせるという手の込み入りようでした。😮‍ 💨

Discussion

piyokopiyoko

ありがとうございます!
index.htmlの内容を公式の方法に変更しておきます。

1回目の地雷は耐えられましたが、2回目の地雷は ウッまたか! ってなりました。