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.

【AWS】Cognitoハンズオン - サーバーレスのウェブアプリケーションを構築する

Last updated at Posted at 2023-07-22

はじめに

Cognito について調べる機会があったので、実際に公式のハンズオンを試してみた!って記事です。
参照:サーバーレスのウェブアプリケーションを構築する

構成は下記の通り。
image.png
Amplify と Cognito は初見なので頑張ります。。

サーバーレスのウェブアプリケーションを構築する

① 静的ウェブサイトをホスティングする

AWS Apmlifyについては コチラ
image.png
今回、静的ウェブサイトのホスティングに AWS Apmlify を使用します。

調べたところ Apmlify 君はかなり便利そう。
AWS バックエンドを数分で作成してくれる上に、色々な機能まで兼ね備えてるとか。。

実際の業務では色々な要件があるので Apmlify 君を使う機会は少ないかもですが、このような便利なサービスがあるとハンズオンも捗るのでありがたいです。

少し話が脱線しましたのでハンズオンに戻ります。

リージョン選択

我らがap-northeast-1(東京リージョン)で作業をしていきます。
image.png

Gitリポジトリを作成

CodeCommit を使用してアプリケーション コードを保存します。(GitHubでも可)

AWSマネコンから CodeCommit > リポジトリ > リポジトリを作成 の順にクリック

今回はsample-repoというリポジトリを作成しました。

CodeCommit の HTTPS Git 認証

IAM > ユーザ > 自身のIAMユーザ > セキュリティ認証情報 タブの順の開きます。
image.png

認証情報の生成から認証情報を生成して、csvファイルをダウンロードするか、情報を控えておきます。
image.png

Gitリポジトリにデータを追加

筆者は Windows11 なので人によっては一部手順が異なります。
そこら辺は調べてください。

URL のクローンから HTTPSのクローン を選択し、URLをコピーします。
image.png

自身のPCに適当に作業用のフォルダを作成して、パスの部分にcmdと入力しEnterを押下
image.png

ターミナルで git clone を実行します。
git コマンドが使えない方はインストールしてください。

git clone <HTTPS URL>

コマンドを実行すると下記の様に認証を促されます。
先ほど生成した認証情報を入力してContinueをクリック。
image.png

リポジトリが空なので warning が出力されますが気にしない。

C:\Users\onish\OneDrive\デスクトップ\AWS\Cognito>git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/sample-repo
Cloning into 'sample-repo'...
warning: You appear to have cloned an empty repository.

ここにデータを追加していきます。

# ディレクトリをリポジトリに変更
cd sample-repo

# ソースコードのダウンロード(aws コマンドが実行できるようにしておいてください)
aws s3 cp s3://wildrydes-us-east-1/WebApplication/1_StaticWebHosting/website ./ --recursive

# add
git add .

# commit
git commit -m 'new'

# push 
git push

push 出来たので CodeCommit のリポジトリを確認
ソースコードが追加されているので問題なし!
image.png

Amplifyコンソールでウェブホスティングを有効にする

やっと Amplify 君の出番。 お待たせ・・・。

使用を開始するから右側の Amplify ホスティングを選択
image.png

CodeCommit を選択後、先ほどのリポジトリとブランチを選択し、次へ を押下
image.png

下図のチェックボックスにチェックをつけてデプロイします。
デプロイには数分かかります。
image.png

この間に次の作業の準備をします。

サイトを変更する

Amplify コンソールは、接続されたリポジトリへの変更を検出すると、アプリを再構築して再デプロイします。

index.html を少し修正して、実際に自動デプロイされるかを確認します。
修正内容はなんでもいいです。。

sample-repo/index.html
<!-- 修正前 (7行目) -->
<title>Wild Rydes</title>

<!-- 修正後 -->
<title>Wild Rydes - hands on!!</title>

修正後に再度 push します。
ただ、その前にAmplify によってウェブサイトがデプロイされていることを確認してから実施します。

デプロイまでチェックが付いていると、画像の左下辺りにURLが表示されておりますのでそちらをクリック
image.png

こんなのが表示されれば OK です。
image.png

確認できたので push します。

# add
git add .

# commit
git commit -m 'update title'

# push 
git push

デプロイ完了後に確認するとタイトルが変わってる!便利ですね。
image.png

② ユーザを管理する

検証済み ID の作成

Cognito でメール送信機能を使用するので、SESに検証済みのIDを作成しておきます。

Amazon SES > 設定: 検証済み ID > ID の作成 の順にクリック
IDタイプはEメールを選択し、自身が使用しているメールアドレスを入力。Gメールとかで OK です。
example が誤字ってるなんて言わないでくださいね。。
image.png

するとメールが送られてくるので URL をクリックして認証完了。

Cognito ユーザープールの作成

やっと Cognito です。
記事書きながらハンズオンしているので、ここまでで1時間弱掛かっており心が折れそうです(笑)

ユーザーのアカウントを管理するための Amazon Cognito ユーザープールを作成します。
顧客が新規ユーザーとして登録し、電子メール アドレスを確認し、サイトにサインインできるページを展開します。
image.png

Cognito は、ユーザーを認証するために 2 つの異なるメカニズムを提供します。

① Cognito ユーザー プール
Cognito ユーザー プール使用して、アプリケーションにサインアップおよびサインイン機能を追加可能

②Cognito ID プール
Facebook、Twitter、Amazon などのソーシャル ID プロバイダー、SAML ID ソリューション、または独自の ID システムを使用してユーザーを認証可能

以下は ChatGPT のサインイン画面。
image.png

Emailアドレスでサインインする場合は、Cognito ユーザプール、
Google や Apple のアカウントでサインインするのが Cognito ID プール ってイメージかな?

Cognito ユーザプールを作成し、アプリをユーザプールに統合する

Amazon Cognito > ユーザープール > ユーザープールを作成 の順にクリック
ここら辺の作業は公式ハンズオンと異なります。(コンソールのバージョンが異なるため)

・認証プロバイダー
ユーザ名にチェックを付けて「次へ」を押下

・セキュリティ要件を設定
多要素認証 はMFAなしを選択し「次へ」を押下

・サインアップエクスペリエンスを設定
デフォルトのまま「次へ」を押下

・メッセージ配信を設定
E メールプロバイダーは SES を選択し、先ほど作成した検証済み ID を選択して「次へ」を押下

・アプリケーションを統合
ユーザープール名:sample-user-pool
アプリケーションクライアント名:WildRydesWebApp
それ以外はデフォルトで「次へ」を押下しユーザプールを作成します。

Webサイト構成を更新する

sample-repo/js/config.js ファイルに Cognito の情報を記述していきます。
必要な情報は以下の2つ。
① userPoolId
作成したらすぐに確認できる個所にあります。
image.png

② userPoolClientId
作成したユーザプールを選択後、アプリケーションの結合タグを選択
一番下までスクロールするとあります。
image.png

sample-repo/js/config.js
window._config = {
    cognito: {
        userPoolId: 'ap-northeast-1_ZuNYdCNEU', // e.g. us-east-2_uXboG5pAb
        userPoolClientId: '3e2a1i2ak2i7ph8vc8f1voceb4', // e.g. 25ddkmj4v6hfsfvruhpfi7n4hv
        region: 'ap-northeast-1' // e.g. us-east-2
    },
    api: {
        invokeUrl: '' // e.g. https://rc7nyt4tql.execute-api.us-west-2.amazonaws.com/prod',
    }
};

修正後は push します。

# add
git add .

# commit
git commit -m "new config"

# push
git push

検証

Web サイトのドメインの下にある/register.htmlにアクセスするか、Giddy Up!を押下します。

自身が所有しているEメールアドレスがある場合はそちらを入力してユーザ登録を行います。
(検証済みIDで使用したアドレスを使用する)
image.png

LET'S RYDE を押下すると、認証ページに飛ばされます。
6桁の認証コードが送信されているので、メールアドレスと認証コードを入力すると下記メッセージのポップアップが表示されます。

Verification successful. You will now be redirected to the login page

認証に成功したのでログインページにリダイレクトされますので、
メールアドレスとパスワード入力してログインを行います。

以下のようなものが出力されたら OK
image.png

Cognito のユーザプールを確認すると、ユーザが登録されております。
image.png

そもそもですが、Cognito は 認証・認可の機能を提供するサービスなので、
認証に成功するとトークンが返ってくるって感じですね。

このトークンを利用して API Gateway で API を呼び出すことが出来るって感じかー!と少し理解してきた。

③ サーバレスバックエンドを構築する

続いてはバックエンドの構築。
今回は DynamoDB と Lambda を構築します。
image.png

DynamoDB テーブルを作成する

DynamoDB > テーブル > テーブルの作成 の順にクリックします。
下記のパラメータを設定し、残りはデフォルトでテーブルを作成します。

パラメータ 設定値
テーブル名 Rides
パーティションキー RideId

作成後に、テーブルの概要から ARN を控えておきます。(後で使います)
image.png

Lambda 関数の IAM ロールを作成する

Lambda 関数にアタッチする IAM ロールを作成します。

IAM > ロール > ロールを作成 の順にクリックします。

・信頼されたエンティティを選択
下記のパラメータを設定し「次へ」を押下

パラメータ 設定値
信頼されたエンティティタイプ AWS のサービス
ユースケース Lambda

・信頼されたエンティティを選択
AWSLambdaBasicExecutionRoleにチェックを付けて「次へ」を押下

・名前、確認、および作成
下記のパラメータを設定し「作成」を押下

パラメータ 設定値
ロール名 WildRydesLambda

IAM ロールにインラインポリシーを追加します。

IAM > ロール > WildRydesLambda の順にクリックします。
許可ポリシーの右側に「許可を追加」というメニューがあるので、そちらからインラインポリシーを作成
image.png

サービス検索にDynamoDBと入力しDynamoDB を選択。

検索欄にPutItemと入力し、PutItemにチェックを付ける。
image.png

その後、ARN を追加をクリックします。
image.png

テキストに切り替えて、先ほど控えたARNを張り付けます。
image.png

「次へ」を押下

下記のパラメータを設定し「ポリシーを作成」を押下

パラメータ 設定値
ポリシー名 DynamoDBWriteAccess

これで、Cloudwatch Logs にログを出力するポリシーと、DynamoDB へ書き込むポリシーが付与された、Lambda 用のIAMロールが完成です。

Lambda 関数を作成する

Lambda > 関数 > 関数の作成 の順にクリックします。

下記のパラメータを設定し「関数の作成」を押下

パラメータ 設定値
関数名 RequestUnicorn
ランタイム Node.js 16.x
デフォルトの実行ロールの変更 WildRydesLambda

作成したLambda関数のコードを修正します。
index.js の内容を下記に修正し、「Deploy」を押下

const randomBytes = require('crypto').randomBytes;

const AWS = require('aws-sdk');

const ddb = new AWS.DynamoDB.DocumentClient();

const fleet = [
    {
        Name: 'Bucephalus',
        Color: 'Golden',
        Gender: 'Male',
    },
    {
        Name: 'Shadowfax',
        Color: 'White',
        Gender: 'Male',
    },
    {
        Name: 'Rocinante',
        Color: 'Yellow',
        Gender: 'Female',
    },
];

exports.handler = (event, context, callback) => {
    if (!event.requestContext.authorizer) {
      errorResponse('Authorization not configured', context.awsRequestId, callback);
      return;
    }

    const rideId = toUrlString(randomBytes(16));
    console.log('Received event (', rideId, '): ', event);

    // Because we're using a Cognito User Pools authorizer, all of the claims
    // included in the authentication token are provided in the request context.
    // This includes the username as well as other attributes.
    const username = event.requestContext.authorizer.claims['cognito:username'];

    // The body field of the event in a proxy integration is a raw string.
    // In order to extract meaningful values, we need to first parse this string
    // into an object. A more robust implementation might inspect the Content-Type
    // header first and use a different parsing strategy based on that value.
    const requestBody = JSON.parse(event.body);

    const pickupLocation = requestBody.PickupLocation;

    const unicorn = findUnicorn(pickupLocation);

    recordRide(rideId, username, unicorn).then(() => {
        // You can use the callback function to provide a return value from your Node.js
        // Lambda functions. The first parameter is used for failed invocations. The
        // second parameter specifies the result data of the invocation.

        // Because this Lambda function is called by an API Gateway proxy integration
        // the result object must use the following structure.
        callback(null, {
            statusCode: 201,
            body: JSON.stringify({
                RideId: rideId,
                Unicorn: unicorn,
                Eta: '30 seconds',
                Rider: username,
            }),
            headers: {
                'Access-Control-Allow-Origin': '*',
            },
        });
    }).catch((err) => {
        console.error(err);

        // If there is an error during processing, catch it and return
        // from the Lambda function successfully. Specify a 500 HTTP status
        // code and provide an error message in the body. This will provide a
        // more meaningful error response to the end client.
        errorResponse(err.message, context.awsRequestId, callback)
    });
};

// This is where you would implement logic to find the optimal unicorn for
// this ride (possibly invoking another Lambda function as a microservice.)
// For simplicity, we'll just pick a unicorn at random.
function findUnicorn(pickupLocation) {
    console.log('Finding unicorn for ', pickupLocation.Latitude, ', ', pickupLocation.Longitude);
    return fleet[Math.floor(Math.random() * fleet.length)];
}

function recordRide(rideId, username, unicorn) {
    return ddb.put({
        TableName: 'Rides',
        Item: {
            RideId: rideId,
            User: username,
            Unicorn: unicorn,
            RequestTime: new Date().toISOString(),
        },
    }).promise();
}

function toUrlString(buffer) {
    return buffer.toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}

function errorResponse(errorMessage, awsRequestId, callback) {
  callback(null, {
    statusCode: 500,
    body: JSON.stringify({
      Error: errorMessage,
      Reference: awsRequestId,
    }),
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  });
}

以下のようなイメージです。
image.png

実装を検証する

Deployの左にある、Testをクリックします。
イベント名にTestRequestEventと入力し、イベントJsonには下記をコピペし「保存」を押下

{
    "path": "/ride",
    "httpMethod": "POST",
    "headers": {
        "Accept": "*/*",
        "Authorization": "eyJraWQiOiJLTzRVMWZs",
        "content-type": "application/json; charset=UTF-8"
    },
    "queryStringParameters": null,
    "pathParameters": null,
    "requestContext": {
        "authorizer": {
            "claims": {
                "cognito:username": "the_username"
            }
        }
    },
    "body": "{\"PickupLocation\":{\"Latitude\":47.6174755835663,\"Longitude\":-122.28837066650185}}"
}

その後、Test を実行し下記のレスポンスが出力されれば OK です。

{
  "statusCode": 201,
  "body": "{\"RideId\":\"ctgrS1HMmBFsrBUkiCgNGQ\",\"Unicorn\":{\"Name\":\"Rocinante\",\"Color\":\"Yellow\",\"Gender\":\"Female\"},\"Eta\":\"30 seconds\",\"Rider\":\"the_username\"}",
  "headers": {
    "Access-Control-Allow-Origin": "*"
  }
}

④ RESTful API をデプロイする

続いては API Gateway を作成します。
image.png

新しい REST API を作成する

API Gateway > API > REST API > 構築 の順にクリックします。
image.png

下記のパラメータを設定し「APIの作成」を押下

パラメータ 設定値
プロトコルを選択する REST
新しい API の作成 新しい API
API 名 WildRydes
エンドポイントタイプ エッジ最適化

オーソライザーを作成する

左側のナビゲーションでオーソライザーを選択後、下記のパラメータを設定し「作成」を押下

パラメータ 設定値
名前 CognitoAuth
タイプ Cognito
ユーザープール sample-user-pool
トークンのソース Authorization
image.png

新しいリソースとメソッドを作成する

左側のナビゲーションでリソースを選択後、
アクションのドロップダウンからリソースの作成を選択する。
image.png

下記のパラメータを設定し「リソースの作成」を押下

パラメータ 設定値
リソース名 ride(リソース パスが[ride]に設定されていることを確認します)
API Gateway CORS を有効にする 有効

作成したrideリソースを選択した状態で、アクションからメソッドの作成を選択する。
image.png

プルダウンからPOSTを選択し、チェックマークをクリック

下記のパラメータを設定し「保存」を押下

パラメータ 設定値
統合タイプ Lambda関数
Lambda プロキシ統合の使用 有効
Lambda 関数 RequestUnicorn

下記のポップアップが表示されるので OK を選択
image.png

左上のメソッドリクエストを選択
image.png

認可:なしの設定を先ほど作成したCognitoAuthに変更します。

API をデプロイする

アクション > API のデプロイの順にクリックします。
image.png

下記のパラメータを設定し「デプロイ」を押下

パラメータ 設定値
デプロイされるステージ 新しいステージ
ステージ名 prod

左ナビゲーションでステージを選択
prod をクリックするとprod ステージエディターが表示されるので、こちらの URL をメモします。
image.png

Web サイト構成を更新する

config.js のinvokeUrlに先ほどメモした URL を記述します。

sample-repo/js/config.js
window._config = {
    cognito: {
        userPoolId: 'ap-northeast-1_ZuNYdCNEU', // e.g. us-east-2_uXboG5pAb
        userPoolClientId: '3e2a1i2ak2i7ph8vc8f1voceb4', // e.g. 25ddkmj4v6hfsfvruhpfi7n4hv
        region: 'ap-northeast-1' // e.g. us-east-2
    },
    api: {
        invokeUrl: 'https://dgo212off5.execute-api.ap-northeast-1.amazonaws.com/prod' // e.g. https://rc7nyt4tql.execute-api.us-west-2.amazonaws.com/prod',
    }
};

修正後は push します。

# add
git add .

# commit
git commit -m "update config"

# push
git push

動作確認

ArcGISのバージョンが上がっているため、ride.htmlを修正する必要があるらしい。
2か所修正する必要があるので、下記の通り修正してください。(4.3 ⇒ 4.6 になっただけ)

sample-repo/ride.html
修正前:https://js.arcgis.com/4.3
修正後:https://js.arcgis.com/4.6

修正後はいつもの git push まで実施します。(割愛)

Web サイトのドメインの下にある/ride.htmlにアクセスします。
アクセス後に下記の画面が表示された場合は、ArcGIS アカウント作成ページから作成してください。
image.png

するとこんな感じで地図が表示される!!
image.png

適当にどこかクリックした後、
右上のRequest Unicornをクリックするとユニコーンが飛んできます(笑)

ユニコーン到着後に DynamoDB を確認するとレコードが追加されてます。
image.png

⑤ リソースを終了する

作成したリソースを削除していきます。

削除は難しくないので、コチラを参考にして下さい。。

削除しないと料金がかかるので気を付けてください。

また、使用しなければ料金はかかりませんが、CodeCommitの削除も忘れずに。

感想

結局 Cognito って何ぞや!という話ですが、
Cognitoは認証・認可機能を提供するAWSサービスという理解でいいですかね。

API Gateway で認可を使うのであれば、紐づけ可能な Cognito はものすごく便利ですね!

認証・認可については コチラ を参考にしてください。

さいごに

無事ハンズオンを終えることが出来ました。
記事にするのはかなり疲れましたがいざ完走するといい気分です(笑)

やっぱりAWSは便利ですし初心者でも使いやすいので本当にいいサービスですね!

定期的にAWSの記事を更新していくので他の記事もぜひご覧ください。

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?