0
0

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 1 year has passed since last update.

Amazon Cognitoでユーザ認証機能をサクっと作ってみる

Posted at

はじめに

Amazon Cognitoはユーザ認証・認可機能を提供するサービスです。ユーザ認証はCognitoの機能で行うことも、GoogleやFacebookなどのソーシャルプロバイダーと連携して行うこともできます。
認証が完了したユーザにリソースへのアクセス権を与えるのが認可で、Cognitoを使うとユーザ認証から認証完了後にAWSリソースへのアクセス権の割り当てるところまでをワンストップ、且つ簡単に実装できます。ここではその一例としてAWS S3にファイルアップロードできるアプリ(ログイン機能付き)を作成してみたいと思います。

手順

  1. Cognitoユーザプールを作成する
  2. CognitoIDプールを作成する
  3. S3を作成する
  4. IAMロールを編集する
  5. Webアプリを作成する
  6. ログインユーザを登録する

Cognitoユーザプールを作成する

Cognitoユーザプールとは前述のユーザ認証機能で使用するデータプールになります。ユーザプールを作成すると、認証情報(ID/パスワード)が登録できるようになります。ユーザプールはAWSコンソールから数ステップで作成できます。

ユーザプールの作成方法

  • AWSコンソールにログインし、Amazon Cognitoのサービスを検索し、メニュー画面から「ユーザプールの管理」を選択します。
  • ユーザプールの管理画面の右上にある「ユーザプールを作成する」ボタンを押すと、作成画面が現れるため、プール名に適当な名前をつけて、「デフォルトを確認する」から設定を行います。
  • デフォルト設定が表示されるので、そのままで「プールの作成」ボタンを押します。
  • プールが作成されるので、左側のメニューから全般設定-アプリクライアントを選択します。
  • 「アプリクライアントを追加」を選択すると、アプリクライアントの設定画面が表示されます。アプリクライアント名に適当な名前を付けてから、クライアントシークレットを生成のチェックボックスを外した上で「アプリクライアントの作成」を押します。
  • 次の画面で表示される「アプリクライアント ID」をメモっておきます。

これでユーザプールが作成できました。

CognitoIDプールを作成する

CognitoIDプールとはユーザ認可機能の方で使用するデータプールになります。認証済みのユーザーに対して、任意のIAMロールを割り当てることでIAMロールで定義されたリソースへのアクセスが可能になります。認証プロバイダーとして、上記で作成したユーザプールを紐付けることで、ユーザプールで認証を行い、IDプールで認可するという連携が可能になります。

IDプールの作成方法

  • AWSコンソールからAmazon Cognitoのサービスを検索し、メニュー画面から「IDプールの管理」を選択します。
  • IDプール作成画面が表示されるので、適当なIDプール名を付与し、認証IDプロバイダーのCognitoタブのところに上で作成したユーザプールIDとアプリクライアントIDを入力した上で、「プールの作成」ボタンを押します。
  • 「Identify the IAM roles to use with your new identity pool」という画面が現れます。ここでは未認証/認証済みのユーザに割り当てるIAMロールを定義します。IAMロール名はそれぞれCognito_(IDプール名)Unauth_Role/Cognito_(IDプール名)Auth_Roleという名前が割り当てられます。ここでは何も変えずに「許可」ボタンを押します。

これでIDプールが作成できました。

S3を作成する

ローカルファイルのアップロード先となるS3バケットを作成します。

S3の作成方法

  • AWSコンソールからS3のサービスを検索し、メニュー画面の右上の方にある「バケットを作成」ボタンを押します。
  • 適当なバケット名を付けて、他はデフォルトのままで「バケットを作成」ボタンを押します。

ちなみにデフォルトではCORSが許可されていないため、外部(別ドメイン)からバケットにファイルアップロードしようとすると失敗します。このため作成したバケットにCORS設定を行います。

CORSの設定方法

  • 作成したバケットの「アクセス許可」タブの一番下にある「Cross-Origin Resource Sharing (CORS)」の編集ボタンを押します。
  • 開いたエディタに以下の内容をペーストして「変更の保存」ボタンを押します。
[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "HEAD",
            "GET",
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": [
            "ETag"
        ]
    }
]

これでS3バケットが作成できました。

IAMロールを編集する

IDプール作成時に割り当てられたIAMロールについては、デフォルトでは何の権限も付与されていません。このため、認証済みロール(Cognito_(IDプール名)Auth_Role)に対して、S3バケットへのアクセス権を付与します。これを行うことで、ユーザ認証が完了するとS3バケットへのアクセスが可能となります。

IAMロールの編集方法

  • AWSコンソールからIAMのサービスを検索し、左側のメニューバーにある「ロール」を選択します。
  • 「Cognito_(IDプール名)Auth_Role」で検索を行い、表示結果を選択します。
  • 許可タブの右上にある「許可を追加」ボタンを押して、更に表示される「ポリシーをアタッチ」を選択します。
  • 「S3」で検索を行い、表示結果から「AmazonS3FullAccess」のチェックボックスにチェックを入れて、「ポリシーをアタッチ」ボタンを押します。

これで認証済みIAMロールへの権限付与ができました。

Webアプリを作成する

AWS側の準備ができたので、Webアプリ(React)を作成します。

アプリはCreateReactAppを使って構築します。

npx create-react-app my-app
cd my-app

AWS SDKを使うため事前にインストールしておきます。

npm i aws-sdk
npm i amazon-cognito-identity-js

なおSDKの設定については下記を参照してください。
https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/configuring-the-jssdk.html

次にmy-appディレクトリに作成されたファイル群のうち、以下のファイルについて内容を書き換えます。

/public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>Cognito demo app</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
/src/App.jsx
import './App.css';
import { useState } from 'react'

var AWS = require("aws-sdk");
var AmazonCognitoIdentity = require('amazon-cognito-identity-js');

function App() {

  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [s3Client, setS3Client] = useState('')
  const [loginFlg, setLoginFlg] = useState(false)
  const [uploadFile, setUploadFile] = useState('')

  // AWS関連のパラメータ設定
  const Region = '(AWSリージョン名)'
  const IdentityPoolId = '(CognitoIDプールのID)' 
  const poolData = {
    UserPoolId: '(CognitoユーザプールのID)'
    ClientId: '(ユーザプールのアプリクライアントID)', 
  };
  const CognitoId = 'cognito-idp.(リージョン名).amazonaws.com/(ユーザプールID)'
  const BucketName = '(S3バケット名)'

  // ログイン用の関数
  const login = () => {
    try {
      // 認証情報の設定
      const authenticationData = {
        Username: email,
        Password: password,
      }
      const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
        authenticationData
      ) 
      const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData)
      const userData = {
        Username: email,
        Pool: userPool,
      }
      const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData)
      
      // ユーザ認証
      cognitoUser.authenticateUser(authenticationDetails, {
        onSuccess: function(result) {
  
          // 認証成功の場合、認可情報をSDKに設定
          const idToken = result.getIdToken().getJwtToken()
          AWS.config.region = Region
          AWS.config.credentials = new AWS.CognitoIdentityCredentials({
            IdentityPoolId: IdentityPoolId, 
            Logins: {
              [CognitoId]: idToken,
            },
          })
      
          // 認可情報をSDKに反映
          AWS.config.credentials.refresh(error => {
            if (error) {
              alert('Login failure!')
              console.error(error);
            } else {
              const s3 = new AWS.S3({
                apiVersion: "2006-03-01",
                params: { Bucket: BucketName }
              });
              setS3Client(s3)
              setLoginFlg(true)
              alert('Successfully login!')
            }
          });
        },
      
        onFailure: function(err) {
          alert('Login failure!')
          console.log(err.message || JSON.stringify(err));
        },
      });
    } catch(e) {
      alert('Login failure!')
      console.log(e)
    }
  }

  // ログアウト用の関数
  const logout = () => {
    try {
      // 認証情報の設定
      const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData)
      const userData = {
        Username: email,
        Pool: userPool,
      }
      const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData)
  
      // ログアウト
      cognitoUser.signOut();
      setS3Client('')
      setLoginFlg(false)

      alert('Successfully logout!')
    } catch(e) {
      alert('Logout failure!')
      console.log(e)
    }
  }

  // ファイルアップロード用の関数
  const fileUpload = async() => {
    try {
      const upload = new AWS.S3.ManagedUpload({
        params: {
          Bucket: BucketName,
          Key: uploadFile.name,
          Body: uploadFile
        }
      })

      var promise = upload.promise();

      promise.then(
        function(data) {
          alert("Successfully uploading file!");
          setUploadFile('')
        },
        function(err) {
          console.log(err)
          return alert("There was an error uploading file!");
        }
      );
    } catch(e) {
      console.log(e)
    }
  }

  if(!loginFlg){
    return (
      <div className="App py-4">
        <div className="container">
          <h1 className='mb-5'>Login</h1>
          <div className="mb-3">
            <label htmlFor="email" className="form-label">Email address</label>
            <input value={email} onChange={(e) => setEmail(e.target.value) } type="email" className="form-control" id="email" placeholder="name@example.com"/>
          </div>
          <div className="mb-3">
            <label htmlFor="password" className="form-label">Password</label>
            <input value={password} onChange={(e) => setPassword(e.target.value)} type="password" className="form-control" id="password"/>
          </div>
          <button type="button" className="btn btn-primary" onClick={login}>ログイン</button>
        </div>
      </div>
    );
  } else {
    return (
      <div className="App py-4">
        <div className="container">
          <h1 className='mb-5'>ファイルアップロード</h1>
          <div className="mb-3">
            <input onChange={(e) => setUploadFile(e.target.files[0])} className="form-control" type="file" id="formFile"/>
          </div>
          <button type="button" className="btn btn-primary" onClick={fileUpload}>アップロード</button>
          <button type="button" className="btn btn-secondary mx-2" onClick={logout}>ログアウト</button>
        </div>
      </div>
    );
  }
 
}

export default App;

ローカルサーバを起動します。

npm start

http://localhost:3000/にアクセスして、以下の画面が表示されれば成功です。
image.png

ログイン情報を登録する

最後にCognitoユーザプールにログイン情報(Eメール・パスワード)を登録します。

登録方法

  • AWSコンソールから先ほど作成したCognitoユーザプールを選択する。
  • 左側のメニューバーから全般設定-ユーザとグループを選択する。
  • 「ユーザ」タブから「ユーザの作成」ボタンを押す。
  • ユーザ名と仮パスワードを入力した後、「Eメールを検証済みにしますか?」以外のチェックを外して「ユーザの作成」を押す。

アカウントのステータス変更

これでログイン情報が作成されますが、初期状態ではアカウントのステータスが「FORCE_CHANGE_PASSWORD」(初期パスワードの変更が必要)となっており、このままでは使えません。

本来初期パスワード変更画面をアプリ側で作って対応する必要がありますが、以下のようにAWS CLIから「CONFIRMED」にステータスを変える方法もありますので、こちらでは割愛します。

これでユーザ登録が完了しました。Webアプリからログインできるようになっているはずですので、お試しください!

参照

AmazonCognitoデベロッパーガイド
AWS SDK for Javascryptデベロッパーガイド
amazon-cognito-identity-js

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?