40
41

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 3 years have passed since last update.

Cognitoを使ってAPI Gatewayのアクセス認証をしてみた

Posted at

はじめに

これまでS3のバケットポリシーとAPI Gatewayのリソースポリシーでアクセス元のIP制限をかけていたため、許可されているIPアドレスからでないと、アクセスし、APIをたたけないようになっていました。しかし、今回訳あってIP制限を外すことになってしまい、APIを誰でも叩き放題になってしまったので、解決策を考えました。

方法検討

API Gatewayで使えるアクセスを認証

APIのアクセスを認証する方法を調べたところ以下の3つが出てきました。

  • Cognito
  • Lambdaオーソライザー
  • IAM認証

Cognito

Cognitoユーザープールで認証時にユーザープールトークンが発行され、そのトークンを使用して認証する方法です。

わたしが思うユースケース
  • ユーザー認証にCognitoを使用しているとき

Lambdaオーソライザー

API Gatewayを叩いた時に、認証用のLambda関数を呼び、認証が通れば、実行したいAPI(今回だとLambda関数)が実行されるようになるという方法です。

わたしが思うユースケース
  • Auth0などのCognito外の認証プラットフォームを使っているとき

参考

IAM認証

APIの実行権限を付与したIAMユーザーを作成し、IAMユーザーのアクセスキー、シークレットキーを使ってAWS Signature V4 署名を作成し認証する方法です。
ユーザーにIAMロールが付与されていれば、それも使用することができます。

わたしが思うユースケース
  • サーバーからAPIをたたくとき(EC2などIAMロールが使えるときはIAMロールを利用)
  • CognitoのグループでIAMロールを付与しているとき

参考

現在の構成図

アプリケーション部分の構成図は下記の通りです。

Cognito.png

ユーザー認証部分はCognito + Amplifyフレームワークで構築しています。構築の基本部分については「【React】ユーザー認証をCognito + Amplifyで構築してみた」の構築準備編構築完成編をご覧ください。
そして、アプリケーション部分はLambda + RDS Proxy + RDSで実装しています。この構築方法については「祝GA‼︎【Go】Lambda + RDS 接続にRDS Proxyを使ってみた」をご覧ください。

結論

現状、Cognitoユーザープールを使ってユーザー認証をしているので、API Gatewayのアクセス認証にもCognitoを使うことにしました!

手順

既存の構成にAPIのアクセス認証をつけていくので、Cognitoユーザープールを使ってのユーザー認証、API Gatewayを使ってLambdaを実行する部分については既に構築できていることを前提として、下記の流れで進めていきます。ただ、今回はDB操作は行わず、メッセージを送り、メッセージをそのまま返すLambda関数を実行するようにしています。

  1. API Gatewayの設定
  2. フロントの実装

やってみる

1. API Gatewayの設定

まず、API Gatewayのオーソライザーを作成していきます。
API Gatewayのコンソールから、[オーソライザー]を開きます。

スクリーンショット 2021-02-03 21.05.58.png

新規でオーソライザーを作成します。
名前、タイプ、Cognitoユーザープール、トークンのソースを入力し、作成ボタンをクリックします。トークンのソースAuthorizationはリクエストのヘッダーとしてトークンを送るときに使います。

スクリーンショット 2021-02-09 23.10.34.png

次に、作成したオーソライザーはメソッド単位で設定していきます。つまり、複数メソッドがある場合はそれぞれに設定しないとトークンなしでAPI Gatewayを叩けてしまうので注意です。

次のように、[リソース]→[オーソライザーを設定したいメソッド]→[メソッドリクエスト]を開きます。

スクリーンショット 2021-02-13 14.59.33.png

許可の部分に先ほど作ったcognito-authorizerを設定します。選択肢に出てこない場合はリロードなどすると選択肢に出てきます!

スクリーンショット 2021-02-09 23.26.40.png

そして最後にデプロイします!
これでオーソライザーの設定は完了です。

2. フロントの実装

取得したユーザープールトークンをヘッダーにつけてAPI Gatewayをたたく処理を実装します。

axiosのインストール

API Gatewayを叩くのにaxiosを使うために、プロジェクトにaxiosを追加します。

$ yarn add axios

ソースコード

認証時に必要なトークンは下記の方法で取得可能です。

const user = Auth.currentAuthenticatedUser()
const idToken = user.signInUserSession.idToken.jwtToken

このidTokenAuthorizationキーのバリューとしてヘッダーに持たせることで、リクエストが可能になります。

App.js
import React from "react";
import Amplify, {Auth} from 'aws-amplify';
import awsconfig from './aws-exports';
import {withAuthenticator} from "@aws-amplify/ui-react";
import axios from "axios";
import "./App.css"

Amplify.configure(awsconfig);

function App() {
    const API_URL = "<API Gatewayで取得したURL>"
    const [message, setMessage] = React.useState("");
    const [response, setResponse] = React.useState("");

    const handleChange = event => {
        setMessage(event.target.value);
    };

    const handleSubmit = async(event) => {
        const user = await Auth.currentAuthenticatedUser()
        const idToken = user.signInUserSession.idToken.jwtToken
        const headers = {headers: {"Authorization": idToken}};
        axios.post(API_URL, {message: message}, headers)
            .then((response) => {
                if(response.data.message === message){
                    setResponse(response.data.message);
                } else {
                    throw Error(response.data.errorMessage)
                }
            }).catch((response) => {
                alert("登録に失敗しました。もう一度送信してください。");
                console.log(response);
        });
        event.preventDefault();
    }

    return (
        <fieldset>
            <form onSubmit={handleSubmit}>
                <label >
                    <input type="text" value={message} onChange={handleChange} />
                </label>
                <input type="submit" value="送信" />
            </form>
            <div>{response}</div>
        </fieldset>
    );
}

export default withAuthenticator(App);

実行結果

ヘッダーあり

入力欄の下に、Lambdaから返ってきたメッセージが表示されるようになっています。入力した値がLambdaを介して返ってきています!

画面収録-2021-02-09-23.37.09.gif

ヘッダーなし

ちなみに、ヘッダーにidトークンを付けずに実行してみました。

※ API Gatewayをたたくところのみ抜粋

App.js
        axios.post(API_ADD_URL, {message: message})
            .then((response) => {
                if(response.data.message === message){
                    setResponse(response.data.message);
                } else {
                    throw Error(response.data.errorMessage)
                }
            }).catch((response) => {
                alert("送信に失敗しました。もう一度送信してください。");
                console.log(response);
        });

ソースを上記のように変更し、実行すると・・

スクリーンショット 2021-02-03 20.14.36.png

エラーが出て、Lambdaが実行できないことがわかりました!

おわりに

無事に、API Gatewayにアクセス認証をつけることができました!今回はもともとCognitoユーザープールを使ってユーザー認証をやっていたので、Cognitoのオーソライザーを使って簡単に設定することができました。既存のシステムの構成によってこれでIP制限を外しても、セキュリティを担保することができたのではないかと思います!めでたし!

参考

40
41
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
40
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?