【SAM + Cognito + Amplify ライブラリでログイン機能を実装した React アプリを作成】Part3 - API に認証追加とログインページ実装


みなさん、こんにちは。イノベーションLABのハヤシです。

以下の構成の Web アプリをさくっと作る手順をご紹介します。
 バックエンド:Node.js の REST API(Amazon API Gateway / AWS Lambda)
 フロントエンド:TypeScript の React

今回は全 3 回のうちの最終回、認証と、お片付け編です。
必要な部分だけでも、ぜひ参考にしてみてください。

1回目はこちら↓
blog.css-net.co.jp
2回目はこちら↓
blog.css-net.co.jp

1. 前提条件

1-1. 想定読者

「さくっとログイン機能を実装した Web アプリが作りたい」
「せっかくなら SPA + REST API でやりたい」
「AWS SAM や Amplify ライブラリを使ってみたい」
といった方々を想定しています。


必要最低限の方法のみ解説しているので、ほぼサンプルコードのままです。
その後のカスタマイズには、 Node.js や TypeScript / React の知識が必要になります。


とはいえ、この記事は知識があることを前提としていませんので、手順通りに実施していく分にはそれらの知識は不要です。
雰囲気をつかむために、まずはチャレンジしてみるのもおすすめです!

1-2. 前提条件

OS - Windows 10 Pro (Home でも問題ありません)
VSCode - 1.69.1
Git - 2.37.1.windows.1
AWS CLI - 2.7.14
SAM CLI - 1.53.0
Node.js - 16.16.0


aws configure で認証設定がすんでいること
以下コマンドで、「Account」が自分が操作可能な Account ID になっていることを確認する

aws sts get-caller-identity

# Profile を指定している場合は今後のコマンドも全て Profile を指定してください
# aws sts get-caller-identity --profile <Profile名>


この後の手順は特に指示がない限り、エディタは VSCode でターミナルは Git Bash での操作となります

環境構築手順はこちらで紹介しています
blog.css-net.co.jp


2. 認証

2-1. API 実行を認証必須にする(バックエンド)

Cognito を追加して、 API を認証しないと利用できないように設定します。
VSCode のターミナルで作業します。
バックエンド用ディレクトリ(C:\work\tutorial-app\tutorial-app-back)にいる前提で進めていきます。


前回の手順で追加した API Gateway のあとに、 Cognito の設定を追記して、 API Gateway にも Auth 設定を追加しておきます。
以下は、ユーザ名/Eメール でユーザを作成する例です。

template.yaml

  # --------------------
  # API Gateway
  # --------------------
  SampleApi:
    Type: AWS::Serverless::Api
    Properties:
      Name: "tutorial-app-api"
      StageName: dev
      OpenApiVersion: 3.0.2
      EndpointConfiguration: REGIONAL
      Cors:
        AllowMethods: "'GET,POST,DELETE,PATCH,OPTIONS'"
        AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
        AllowOrigin: "'*'"
# ----- 追加↓
      Auth:
        DefaultAuthorizer: SampleCognitoAuth
        AddDefaultAuthorizerToCorsPreflight: false
        Authorizers:
          SampleCognitoAuth:
            UserPoolArn: !GetAtt SampleUserPool.Arn

  # --------------------
  # Cognito
  # --------------------
  # UserPool
  SampleUserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: "tutorial-app-userpool"
      Schema:
      -
        Name: email
        AttributeDataType: String
        Mutable: true
        Required: true
      AutoVerifiedAttributes:
        - email
      UsernameConfiguration:
          CaseSensitive: false
      AccountRecoverySetting:
        RecoveryMechanisms:
        -
          Name: verified_email
          Priority: 1
      DeviceConfiguration:
        DeviceOnlyRememberedOnUserPrompt: true
  # UserPoolClient
  SampleUserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      UserPoolId: !Ref SampleUserPool
      ClientName: "tutorial-app-userpoolclient"
      ExplicitAuthFlows:
        - ALLOW_ADMIN_USER_PASSWORD_AUTH
        - ALLOW_CUSTOM_AUTH
        - ALLOW_USER_SRP_AUTH
        - ALLOW_REFRESH_TOKEN_AUTH
      PreventUserExistenceErrors: ENABLED
      ReadAttributes:
        - email
        - email_verified
      WriteAttributes:
        - email
# ----- 追加↑

Outputs:
  WebEndpoint:
    Description: "API Gateway endpoint URL for dev stage"
    Value: !Sub "https://${SampleApi}.execute-api.${AWS::Region}.amazonaws.com/dev"


ビルド / デプロイをします。

sam build
sam deploy --config-env dev


Cognito のユーザープールが作成されていることを確認します。
マネジメントコンソールで確認します。
◆Cognito -> ユーザープール -> tutorial-app-userpool


この状態で React アプリを起動しているブラウザを更新すると、 401 エラー(認証エラー)でデータが取得できないはずです。

これについてはログインページ実装後に実施します。

2-2. ログインページ実装(フロントエンド)

React の UI ライブラリを使用して、認証を追加していきます。
VSCode のターミナルで作業します。
フロントエンド用ディレクトリ(C:\work\tutorial-app\tutorial-app-front)にいる前提で進めていきます。

参考
Authenticator | Amplify UI for React


上記を参考に、 src/App.tsx を以下のように変更します。

import React from 'react';
import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';

import './App.css';
import Home from './pages/Home';

function App() {
  return (
    <Authenticator signUpAttributes={['email']}>
      {({ signOut, user }) => (
        <Home />
      )}
    </Authenticator>
  );
}

export default App;


サインインページが表示されます。
※まだサインアップ/サインインはできません


作成した Cognito を認証情報として使用するように設定します。
src/aws-exports.js に、「Auth」セクションを追記します。
src/aws-exports.js

const config = {
  Auth: {
    region: process.env.REACT_APP_AWS_AUTH_REGION,
    userPoolId: process.env.REACT_APP_AWS_AUTH_USER_POOL_ID,
    userPoolWebClientId: process.env.REACT_APP_AWS_AUTH_CLIENT_ID
  },
  API: {
      endpoints: [
          {
              name: "MainApi",
              endpoint: process.env.REACT_APP_AWS_API_ENDPOINT_MAIN,
          },
      ]
  }
};

export default config;


作成した Cognito の ID を確認します。
マネジメントコンソールで確認します。
◆Cognito -> ユーザープール -> tutorial-app-userpool
ユーザープールID

アプリケーションの統合 -> アプリクライアントと分析 -> クライアントID


.env ファイルと .env_sample ファイルを以下のように変更します。
※REACT_APP_AWS_API_ENDPOINT_MAIN はもともと設定済み

.env

REACT_APP_AWS_AUTH_REGION="ap-northeast-1"
REACT_APP_AWS_AUTH_USER_POOL_ID="<ユーザープールID>"
REACT_APP_AWS_AUTH_CLIENT_ID="<クライアントID>"
REACT_APP_AWS_API_ENDPOINT_MAIN="<APIエンドポイント>"

.env_sample

REACT_APP_AWS_AUTH_REGION="ap-northeast-1"
REACT_APP_AWS_AUTH_USER_POOL_ID="ap-northeast-1_xxxxxxxxx"
REACT_APP_AWS_AUTH_CLIENT_ID="xxxxxxxxxx"
REACT_APP_AWS_API_ENDPOINT_MAIN="https://xxxxxxxxxx"


フロントエンドのアプリを起動している場合は Ctrl + C で停止させてから、 npm start で改めて起動させます。
※再起動させないと、今設定した env(環境変数)が適用されません


「Create Account」タブをクリックし、ユーザ名とメールアドレスでアカウントを作成します。


登録したメールアドレスに認証コードが届きます。


元の画面は認証コード入力画面に遷移しているはずなので、届いたコードを入力して「Confirm」をクリックします。


ログインが成功すると、 Home 画面が表示されます。(API はまだエラーなのでデータは表示されません)

2-3. API 呼び出しに認証情報を付与(フロントエンド)

VSCode のターミナルで作業します。
フロントエンド用ディレクトリ(C:\work\tutorial-app\tutorial-app-front)にいる前提で進めていきます。

src/pages/Home.tsx

import { API, Auth } from 'aws-amplify'

// ~略~

  const getSampleData = async () => {
    try {
      const apiName = 'MainApi';
      const path = '/';
      const myInit = {
        headers: { 
          Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}`,
        },
      };
    
      const data = await API.get(apiName, path, myInit);
      setSampleData(data)
    } catch (err) { console.log('error getting data') }
  }


ブラウザを更新すると、データが表示されているはずです。(開発者ツールでもエラーが消えています)


ここまでで back / front ともに push しておきましょう。
tutorial-app-front ディレクトリで

git add .
git commit -m "認証追加"
git push origin main

tutorial-app-back ディレクトリで

git add .
git commit -m "認証追加"
git push origin main

3. おまけ

3-1. サインアウト実装

Home 画面にユーザ名の表示とサインアウトボタンを追加しましょう。
VSCode のターミナルで作業します。
フロントエンド用ディレクトリ(C:\work\tutorial-app\tutorial-app-front)にいる前提で進めていきます。


Home.tsx と App.tsx ファイルを以下のように編集します。

src/pages/Home.tsx

import React, { useEffect, useState } from 'react'
import { API, Auth } from 'aws-amplify'
import { CognitoUserAmplify } from '@aws-amplify/ui';
import '@aws-amplify/ui-react/styles.css';

type SampleDataType = [
  {
    id: string;
    name: string;
  }
];
const initialSampleData = {id: "", name: ""};

type HomeProps = {
  signOut: VoidFunction;
  user: CognitoUserAmplify;
};

const Home: React.FC<HomeProps> = (props) => {
  const [sampleData, setSampleData] = useState<SampleDataType>([initialSampleData])

  useEffect(() => {
    getSampleData()
  }, [])

  async function getSampleData() {
    try {
      const apiName = 'MainApi';
      const path = '/';
      const myInit = {
        headers: { 
          Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}`,
        },
      };
    
      const data = await API.get(apiName, path, myInit);
      setSampleData(data)
    } catch (err) { console.log('error getting data') }
  }

  return (
    <div className="App">
      <p>ようこそ、 {props.user.username} さん!</p>
      <p>登録データ一覧</p>
      {
        sampleData.map((data, index) => (
          <div key={data.id ? data.id : index}>
            <p>{data.name}</p>
          </div>
        ))
      }
      <button onClick={props.signOut}>Sign out</button>
    </div>
  );
}

export default Home;


src/App.tsx

import React from 'react';
import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';

import './App.css';
import Home from './pages/Home';

function App() {
  return (
    <Authenticator signUpAttributes={['email']}>
      {({ signOut, user }) => (
        <Home signOut={signOut!} user={user!} />
      )}
    </Authenticator>
  );
}

export default App;


ブラウザを更新すると、サインアウトボタンが表示されます。


クリックすると、サインイン画面に戻ります。
サインインすると Home 画面が表示されることを確認しましょう。

push しておきます。
tutorial-app-front ディレクトリで

git add .
git commit -m "サインアウトボタン追加"
git push origin main

3-2. サインイン画面日本語化

VSCode のターミナルで作業します。
フロントエンド用ディレクトリ(C:\work\tutorial-app\tutorial-app-front)にいる前提で進めていきます。


App.tsx ファイルを以下のように編集します。

src/App.tsx

import React from 'react';
import { I18n } from 'aws-amplify'
import { Authenticator, translations } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';

import './App.css';
import Home from './pages/Home';

I18n.putVocabularies(translations);
I18n.setLanguage('ja');

function App() {
  return (
    <Authenticator signUpAttributes={['email']}>
      {({ signOut, user }) => (
        <Home signOut={signOut!} user={user!} />
      )}
    </Authenticator>
  );
}

export default App;


ブラウザを更新して、日本語化されていることを確認します。



push しておきます。
tutorial-app-front ディレクトリで

git add src/App.tsx
git commit -m "サインイン画面日本語化"
git push origin main

4. リソース削除

4-1. 削除対象のリソース一覧

これで作成編は終わりです。確認が終わったら、作成したものを削除しておきましょう。
今回の手順で AWS 上に作成されているリソースは以下のとおりです。

★…tutorial-app-repo-stack スタックで作成
☆…tutorial-app-back-stack スタックで作成
無印...手動/自動で作成

マネジメントコンソールで確認します。
◆CloudFormation -> スタック

  • ★tutorial-app-repo-stack
  • ☆tutorial-app-back-stack

◆CodeCommit -> リポジトリ

  • ★tutorial-app-front
  • ★tutorial-app-back

◆Lambda -> アプリケーション

  • ☆tutorial-app-back-stack

◆CloudWatch -> ログ -> ロググループ

  • /aws/lambda/tutorial-app-back-stack-getAllItemsFunction-<ランダム文字列>
  • /aws/lambda/tutorial-app-back-stack-putItemFunction-<ランダム文字列>
  • /aws/lambda/tutorial-app-back-stack-getByIdFunction-<ランダム文字列>

※1度も関数を実行していない場合は作成されていない

◆API Gateway -> API

  • ☆tutorial-app-api

◆Cognito -> ユーザープール

  • ☆tutorial-app-userpool

◆DynamoDB -> テーブル

  • ☆tutorial-app-back-stack-SampleTable-<ランダム文字列>

◆IAM -> ロール

  • ☆tutorial-app-back-stack-getAllItemsFunctionRole-<ランダム文字列>
  • ☆tutorial-app-back-stack-getByIdFunctionRole-<ランダム文字列>
  • ☆tutorial-app-back-stack-putItemFunctionRole-<ランダム文字列>

◆S3 -> バケット

  • aws-sam-cli-managed-default-samclisourcebucket-<ランダム文字列>
    • tutorial-app-back-stack/ ディレクトリ

※手動で削除する場合、バケット自体は他で利用している可能性があるので不用意に削除しないこと。

4-2. 削除

VSCode のターミナルで作業します。
フバックエンド用ディレクトリ(C:\work\tutorial-app\tutorial-app-back)にいる前提で進めていきます。


以下のコマンドを実行します。

sam delete --config-env dev


選択肢は以下のとおり。

  • Are you sure you want to delete the stack tutorial-app-back-stack in the region ap-northeast-1 ? [y/N]:
    • y
  • Are you sure you want to delete the folder tutorial-app-back-stack in S3 which contains the artifacts? [y/N]:
    • y


tutorial-app-back-stack スタックと紐づくリソース(上記の☆のリソース)、S3 バケットにアップされたファイルが削除されます。


Stack 削除で削除されないロググループも削除しておきましょう。
以下を選択します(作成されていない場合もあります)。

◆CloudWatch -> ログ -> ロググループ

  • /aws/lambda/tutorial-app-back-stack-getAllItemsFunction-<ランダム文字列>
  • /aws/lambda/tutorial-app-back-stack-putItemFunction-<ランダム文字列>
  • /aws/lambda/tutorial-app-back-stack-getByIdFunction-<ランダム文字列>

対象にチェックを入れ、「アクション」 -> 「ロググループの削除」をクリックします。


確認ダイアログで「削除」をクリックすると削除されます。


CodeCommit のリポジトリも削除する場合は、 tutorial-app-repo-stack スタックを選択し、「削除」をクリックします。
◆CloudFormation -> スタック


確認ダイアログで「スタックの削除」をクリックします。


スタック一覧から消え、ステータスが DELETE_COMPLETE になれば削除完了です。

5. 参考

5-1. 認証

AWS::Cognito::UserPool - AWS CloudFormation
AWS::Cognito::UserPoolClient - AWS CloudFormation