MENU
  • 公式
  • 開発
fav

Cognito UserPoolを使ってAPIを保護しよう

  • シェア
  • ツイートする
  • このサイトをはてなブックマークに追加
  • Pocketに保存
Befcc552 3fd1 45cb a09d 8bfc5a2be99a

こんにちは。北の大地の橋本です。

今回はAPIGatewayを業務アプリケーションで使おうと思った場合に避けられないAPIの保護について書いてみます。

APIを保護する、とは

APIGatewayで作成したAPIに何のアクセス制限もつけなければ、URLさえ判ってしまえば誰でも呼び出すことができます。 これで問題がない場合もありますが、業務システムで使おうと思った場合、 データがだだ漏れになってしまったりしては大変です。

APIGatewayでは、アクセス制御の方法をいくつか用意しています。

http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-control-access-to-api.html

今回は、この中から比較的新しいサービスであるAmazon Cognito ユーザープールを使用してみたいと思います。

APIキーはセキュリティ対策につかえる?

上記ページで「APIGatewayでのアクセス制御」として"APIキー"が記載されていますが、APIキーの説明では、

API キーを、API へのアクセスをコントロールするためのセキュリティメカニズムと見なすべきではありません。

http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-api-keys.html

と書かれていますので、APIキーが必要なAPIを作っても、それは保護されていることにはならないので気をつけましょう。

準備

ManagementConsoleとcliでご自身の環境にアクセスできるようにしてください。

CloudFormationでAPIなどを作る

こちらからCloudFormation用のテンプレートをダウンロードして、ManagementConsoleから新しいStackを作成してください。

https://s3-ap-northeast-1.amazonaws.com/h4a-hashimoto-files/resources/20161031_UserPool/files/apis.json

このCloudFormationテンプレートで作成されるスタックで作られる主要なリソースはこのような感じです。

  • API(アクセス制限無し、アクセス制限有り)
  • Webコンテンツ用S3バケット

Cognito UserPoolはCloudFormationでは作成せずに、後ほど作成します。

S3バケットにWebコンテンツをアップロード

以下のURLからダウンロードしたzipファイルを展開して、CloudFormationで作成されたS3バケットにアップロードしてください。 バケット名は、<StackName>-<AccountId>で作成されています。

https://s3-ap-northeast-1.amazonaws.com/h4a-hashimoto-files/resources/20161031_UserPool/files/web.zip

S3バケットの直下に"noauth"と"withauth"のディレクトリが作成されるようにアップロードしてください。

アップロードしたら、CloudFormationのOutputに表示されている"WebPageNoAuth"と"WebPageWithAuth" のURLにWebブラウザでアクセスして、サンプルページが表示されることを確認してください。

  • WebPageNoAuth:認可なしのサンプルページ
  • WebPageWithAuth:認証・認可ありのサンプルページ

まずは裸のAPIを呼び出してみる

認可無しのサンプルページ(http://<バケット名>.s3-website-ap-northeast-1.amazonaws.com/noauth/index.html)から、 APIを呼び出してみます。 APIのエンドポイントURLには、CloudFornationのOutputから"ApiEndpointNoAuth"の値を設定してください。 "API実行!"ボタンをクリックすると、結果が表示されます。

正常に呼び出されました。では、呼び出す部分のソースを見てみましょう。

var apiEndpoint = document.getElementById('apiendpoint').value;
if (apiEndpoint === '') {
  return;
}
$.ajax(
  apiEndpoint,
  {
    type: 'GET',
    contentType: 'application/json',
    async: false,
    cache: false,
    complete: function(jqXHR, textStatus) {
      document.getElementById('textStatus').value = textStatus;
      document.getElementById('responseText').value = jqXHR.responseText || jqXHR.statusText;
    }
  }
);

単純にHTTPリクエストを投げているだけです。 エンドポイントのURLを知ってしまえば誰でも呼び出すことができます。これでは問題があるだろうということで、 APIに対するアクセス制御を実装します。

APIへのアクセス制御をどのように考える?

今回のサンプルでは、「ログインしたユーザーのセッションだけがAPIを呼び出すことができる」 という要件でアクセス制御を実装します。

この要件を分解すると

  • 認証:ユーザーを識別する(今回はユーザーIDとパスワード)
  • 認可:ログインされたユーザーのみAPIを呼び出せる

の2つの要素から成り立っています。

この要素をそれぞれ以下のサービス・機能で実現します。

  • 認証:Amazon Cognito UserPool(以降UserPool)
  • 認可:APIGatewayのAuthorizer

最初の要件を言い換えると「UserPoolで認証されたユーザーがAPIGatewayのAuthorizer で認可されることによって、APIを呼び出すことができる」となります。

というわけで、まずはUserPoolを作成します。

UserPoolについて詳しくはAWSの開発者ガイドを見てください。 http://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-identity-pools.html

いろいろ機能がありますが、今回は最低限の機能だけ使用します。

UserPoolの準備

UserPoolの作成

UserPoolは、ManagementConsoleから作成します。作成時にはリージョンを"東京"に設定しておいてください。

Pool nameに任意の名称を入力して、"Step through settings"を選択します。ここではPool nameを"h4aPool"としています。

standard attributesの選択ではデフォルト(emailのみon)のまま"Next step"をクリックします。

password strength(パスワードの強度)のオプションをすべてoffにして、パスワードをユルユルにして"Next step"。実際にはきちんと設定してください。

"Do you want to require verification of emails or phone numbers?"でEmailのチェックをoffにして"Next step"。

デフォルトのまま"Next step"

"No"を選択して"Next step"

"Add an app"をクリックする。

"App name"に任意の名前(ここでは"H4AClient"を指定)を設定し、"Generate client secret"をoffにして"Create app"をクリック

"App name"に先ほど設定したものが追加されたことを確認して"Next step"

デフォルトのまま"Next step"

内容を確認して"Create pool"をクリックすると、UserPoolが作成されます。

作成が完了したら、PoolIdとClientIdを確認します。(後ほど使用します)

PoolId

ClientId

ユーザーの登録とConfirmation

ユーザーの登録はCLIで行います。CLIで実行するための設定をaws configureで行っておいてください。

aws cognito-idp sign-up --client-id <作成したClientId> --username user01 --password 00000000 --user-attributes Name=email,Value=<任意のEmailアドレス>
aws cognito-idp admin-confirm-sign-up --user-pool-id <作成したPoolId> --username user01

コマンド実行後、ManagementConsoleでUsersを表示すると、追加したユーザーがstatus=CONFIRMEDで表示されています。

何故わざわざCLIでやるの?

UserPoolをManagementConsoleで操作すると、ユーザーの新規作成やインポートができます。 何故それを使わないのかというと、サンプルの実装が楽だったからですごめんなさい。

インポートされたユーザーのステータスはFORCE_CHANGE_PASSWORDとなります。 このステータスのユーザーはユーザー自身がパスワードを変更する必要があります。 つまり、その分のページが必要なので、省略してしまいました。

CLIからユーザーの作成とConfirmationを行ってしまえば、ユーザーは即座に利用可能となりますが、 CLIで操作する管理者がパスワードを知ってしまうということになります。

この辺は、実際のシステムのセキュリティ要件などからどのように運用するかを検討する必要が有ります。

APIとUserPoolの統合

APIGatewayでは、メソッドごとに認可の方法を設定できます。 (余談ですが、ManagementConsoleの日本語訳がAuthorization→認証となっているところが気になる)

認可の方法としては、以下の3種類を選べますが、今回はCognito User Pool Authorizerを使用します。

  • IAM
  • Custom Authorizer
  • Cognito User Pool Authorizer

最初に実行したCloudFormationでは、認可が必要(に設定をこれからする)<Stack名>Api-WithAuthと 認可が不要な<Stack名>Api-NoAuthの2つのAPIが作成されています。

ここでは、<Stack名>Api-WithAuthのGETメソッドをUserPoolで認証されたユーザーのみ利用を許可するように設定していきます。

Authorizerの作成

APIGatewayのManagementConsoleから<Stack名>Api-WithAuthのオーソライザーを選択します。 "Cognito ユーザープールオーソライザー"を選択し、"Cognitoユーザープール"から、先ほど作成したユーザープールを選択します。

Cognitoユーザープールの選択肢が表示されない場合は、Cognitoリージョンを確認してください。 その他の項目は変更せずに"作成"をクリックします。

メソッドへの設定

<Stack名>Api-WithAuthのGETメソッドの設定ページを表示します。

"認証"(日本語訳がry)から作成したCognito ユーザープールオーソライザー(ここではh4aPool)を選択します。

設定が終わったらAPIをデプロイします。

"デプロイされるステージ"は、既に存在する"prod"を選択してください。

UserPoolで認証してみる

認証

認証・認可有りのサンプルページ(http://<バケット名>.s3-website-ap-northeast-1.amazonaws.com/withauth/index.html) から、APIを呼び出してみます。

まずは、UserPool関連の設定を入力して、ユーザーID・パスワードで認証します。

"認証の結果"に"認証成功"と表示されたら次へ進みます。

なお、この時点でSDKによりLocalStorageに3つのTokenが格納されています。

この中でidTokenを認可のために使用します。

APIを呼び出す

APIのエンドポイントURLには、CloudFornationのOutputから"ApiEndpointWithAuth"の値を設定してください。 "API実行!"ボタンをクリックすると、結果が表示されます。

APIを呼び出す部分は以下のように実装しています。

cognitoUser.getSession(function(err, session) {
  if (err) {
    alert('getSession()でエラー\n' + err);
  } else {
    var idToken = session.getIdToken().getJwtToken();
    $.ajax(
      apiEndpoint,
      {
        type: 'GET',
        contentType: 'application/json',
        headers: {
          Authorization: idToken
        },
        async: false,
        cache: false,
        complete: function(jqXHR, textStatus) {
          document.getElementById('textStatus').value = textStatus;
          document.getElementById('responseText').value = jqXHR.responseText || jqXHR.statusText;
        }
      }
    );
  }
});

変数cognitoUserは、認証の時点で生成しています。

Cognito関連のSDKからgetSession()とすることで、localStorageに格納されたidTokenが取得できます。 なお、CognitoUser#getSession()ではTokenの有効期限を見ており、

  • idTokenの有効期限が切れていなければ、localStorageから返却
  • idTokenの有効期限が切れていれば、localStorageのrefreshTokenを使って、UserPoolからidTokenを再取得

ということを自動的に行ってくれます。 idTokenの有効期間は1時間、refreshTokenの有効期間はUserPoolの設定で変更可能です。

CognitoUser#getSession()で取得できたidTokenを、HTTPリクエストのAuthorizationヘッダに設定して APIを呼び出すと、APIGatewayがUserPoolと連携して、アクセス可否の判定(認可)を行ってくれます。

まとめ

UserPoolは、ユーザー管理、パスワードの照合はもちろん、いろいろなことができるマネージドサービスです!(雑) 一例を挙げると、パスワード強度の設定・チェックを自前で実装しなくて済むだけでも非常に魅力的ではないですか?

UserPoolとAPIGatewayを連携させることで、簡単にAPIを保護できました。 サーバーレスアーキテクチャでは重要なAPIの保護の仕組みがサービスとして提供されているので、これを利用しない手はないですね。

では!

参考

Cognito UserPoolのプログラミングに関してはここが起点になると思います。

https://github.com/aws/amazon-cognito-identity-js

おまけ

idTokenの長い文字列の内容と有効性は、ManagementConsoleで確認することができます。

Befcc552 3fd1 45cb a09d 8bfc5a2be99a

ライタープロフィール

Icon no img sp eaa797ae94e7d611796ffa4eb867f26b3acb2c283f419d9dd5dc5fdd3cc30263

AWS HIGHWAY編集部

AWSに関わる全ての技術者を応援するサイトHIGHWAY for AWS。随時更新中!Amazon Web Servicesに関わるキュレーションサイトを運営しています。
  • シェア
  • ツイートする
  • このサイトをはてなブックマークに追加
  • Pocketに保存
  • シェア
  • ツイートする
  • LINEで送る
  • このサイトをはてなブックマークに追加
  • Pocketに保存

0 Comments

ページの先頭に戻る