Help us understand the problem. What is going on with this article?

Chaliceのすゝめ その2 WebApiに認証を追加する

More than 1 year has passed since last update.

本記事の目的について

前回記事ではChaliceというAWSのサービスについて記載しました。
そこではChaliceのインストールから、簡単なデバッグ方法までの説明をしてますのでまだChaliceに触れたことがない方はそちらをご覧ください。

前回記事

本記事ではWebApiに認証をつける方法について紹介したいと思います。

環境

本記事の環境は

  • MacOS Mojave 10.14.1
  • Python 3.6.7
  • chalice 1.6.1
  • docker 18.09.0

で作成し、httpクライアントは

  • httpie 1.0.0

を使います

ChaliceのWebApiの認証に関する機能

ここに記載がある通り、AWSでは一般的である IAM, CognitoUserPoolに加え、Custom Authorizerを使っての認証を設定することができます。
これは APIGateway が持っている オーサライザーの設定項目と同じようです。

ApiGatewayのオーサライザー設定コンソール
スクリーンショット 2018-11-24 18.20.49.png

本記事では、CognitoUserPool を使った認証について記載したいと思います。

CognitoUserPoolの準備

ChaliceにはCognitoUserPoolを作成する機能がないのでCloudFormationを使って作成したいと思います。

cloudformation ドキュメント
AWS::Cognito::UserPool
AWS::Cognito::UserPoolClient

template.yaml

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  CognitoUserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: ChaliceUserPool
      Policies:
        PasswordPolicy:
          MinimumLength: 8
          RequireLowercase: True
          RequireUppercase: True
          RequireNumbers: False
          RequireSymbols: False
      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: True
  CognitoUserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      ClientName: ChalicePoolClient
      UserPoolId: !Ref CognitoUserPool
      GenerateSecret: False
Outputs:
  CognitoUserPoolArn:
    Value: !GetAtt CognitoUserPool.Arn

was-cliが有効な環境で下記のコマンドを実行します。
S3には適当なバケットを作成してください。
上記のファイルが存在するフォルダで下記のコマンドを実行してください。

$ aws cloudformation deploy --template-file template.yaml --stack-name ChaliceAuthCognitoUserPool --capabilities CAPABILITY_IAM

# Waiting for changeset to be created..
# Waiting for stack create/update to complete
# Successfully created/updated stack - ChaliceAuthCognitoUserPool

Successfully... と表示されればCognitoUserPoolが作成され、
またCloudformationのstackにCognitoUserPoolのArnが下記のように設定されているはずです。

スクリーンショット 2018-12-01 16.32.41.png

後ほどChaliceの設定に使いますのでメモっておいてください。

認証付きWebApiの実装

認証の実装は

  • プロジェクトの作成
  • 環境変数の登録、読み込み
  • Authorizerの実装

のステップを踏むことになります。
※ 以下のステップはchaliceコマンドが使えることを前提に記載しています。
※ 環境のClalice構築方法については前回記事を参考にしてください。

プロジェクトの作成

下記のコマンドを実行してください。

chalice new-project auth-project
$ tree -ar .
.
├── requirements.txt
├── app.py
├── .gitignore
└── .chalice
    └── config.json

のように4つのファイルが出来上がります。

環境変数の登録

Chaliceはconfig.jsonenvironment_variablesのエントリーを登録するとlambdaで環境変数が呼び出せるようになります。

config.json

{
  "version": "2.0",
  "app_name": "auth",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "environment_variables" : {
        "USER_POOL_ARN": "取得したUserPoolArnを指定"
      }
    }
  }
}

上記の設定でUSER_POOL_ARNという環境変数がlambdaの環境に登録されます。

$ chalice deploy

実行後、出来上がったlambdaを見ると

スクリーンショット 2018-12-01 17.32.29.png

のように環境変数が登録されます。

環境変数の呼び出し

pythonから環境変数を呼び出すには以下のように実装します。

import os

os.environ['USER_POOL_ID']

Authorizerの実装

すべてのパーツが揃いましたのでどんどん実装していきます。

app.py

import os

from chalice import Chalice
from chalice import CognitoUserPoolAuthorizer

app = Chalice(app_name='auth')

authorizer = CognitoUserPoolAuthorizer(
    'ChaliceUserPool', provider_arns=[os.environ['USER_POOL_ARN']]
)


@app.route('/', methods=['GET'], authorizer=authorizer)
def authed_index():
    # print(os.environ['USER_POOL_ARN'])
    return {'success': True}

このように、用途に合わせたauthorizer(今回はCognitoUserPoolAuthorizer)をインスタンス化して@app.routeのデコレータの引数として渡せば良いようです。
非常に簡単ですね。

動作確認

まずはローカルで動作確認をしてみましょう

$ chalice local
$ http localhost:8000

# {
#    "success": true
# }

ローカルサーバーを立ち上げて、Authorizerを設定したApiを呼び出すと、呼び出せてしまいました。どうやらローカルではAuthorizerの検証ができないようです。

次にAWSにデプロイしてみます。

$ chalice deploy
$ http $(chalice url)

# HTTP/1.1 401 Unauthorized
# ...
# {
#    "message": "Unauthorized"
# }

のように今度はApiの呼び出しが 401 の認証エラーになったようです。

ApiGateWayを確認してみると

スクリーンショット 2018-12-01 17.01.00.png

スクリーンショット 2018-12-01 17.04.31.png

のようにApiGatewayにオーサライザーが作成され、対象のApiにはそのオーサライザーが COGNITO_USER_POOL で設定されていることが確認できます。

次にCognitoUserPoolのトークンを取得して Authorization ヘッダーに CognitoUserPool が発行したトークンを付加してリクエストを送信してみましょう。

トークンの取得

トークンの取得、今回はあらかじめユーザーをコンソール上で作成してそのユーザーに対してNodo.js用のAWSのSDKを使ってトークンを取得します。(Clientの用途に合わせて、SDKを選択してください。今回は記載していませんが、React, Android, iOS で実装したい場合はamplifyが良さそうです。)

package.json

{
  "name": "nodejs",
  "version": "1.0.0",
  "description": "get token form your userpool",
  "main": "get_tokne.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "tukky",
  "license": "ISC",
  "dependencies": {
    "amazon-cognito-identity-js": "^2.0.27",
    "node-fetch": "^2.2.0"
  }
}

get_token.js

const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
global.fetch = require('node-fetch');

const poolData = {
  UserPoolId: '<Set Your UserPoolId>',
  ClientId: '<Set Your UserPool Client Id>'
};

const userName = '<set your user name>'

const authenticationData = {
  Username: userName,
  Password: '<set your user pool password>'
};

const userData = {
  Username: userName,
  Pool: new AmazonCognitoIdentity.CognitoUserPool(poolData)
}
const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);

cognitoUser.authenticateUser(authenticationDetails, {
    onSuccess: (result) => {
      const idToken = result.getIdToken().getJwtToken();
      console.log(idToken);
    },
    onFailure: (err) => {
      console.log(err);
    },
    newPasswordRequired: (userAttributes, requiredAttributes) => {
      cognitoUser.completeNewPasswordChallenge(authenticationData.Password, {}, this);
    }
});

get_token.jsに対象となる

  • UserPoolId
  • UserPoolClientId
  • UserName
  • UserPassword

get_token.jsに設定してください。
package.jsonget_token.js を同一のフォルダに配置し

$ npm install
$ node get_token.js
# eyJ...

と実行するとeyJから始まる目的のトークンが取得できます。

トークン付きリクエストの発行

先ほど取得したトークンを付加してリクエストしてみましょう

$ http $(chalice url) Authorization:eyJ...

# HTTP/1.1 200 OK
# {
#    "success": true
# }

のように認証が成功し無事Apiが呼び出すことができました。

まとめ

Chaliceは非常に便利なのですが、機能を使いこなすためには他のAWSに関する知識も必要になります。
また、今回はCloudformationを使ってUserPoolの生成をしましたが、CI/CDツールで運用したり、デプロイ担当と開発担当が分かれているとCloudformationの実行手順や、環境変数の手順ななどのルールを決めないと実際の運用時に問題になりそうです。
(その際はconfig.jsonに自動で環境変数を追加するようなスクリプトが必要になると思います。)

このような問題を抱えながらも自分はChaliceを積極的に使っていきたいと思います。
理由は開発者観点からすると、コードと環境構築が一体になっているのは魅力的で、作りたいものがすぐに環境構築を気にせずに作れるからです。

次回はDynamoDBとS3の連携に関して記述できればと考えています。

ここまで読んでいただきありがとうございました。
開発に少しでも役立てば幸いです。

おまけ

このおまけはNodejsの環境の構築に関するものです、すでにNodejsの環境がある場合はそちらをお使いください。
Nodejsの環境構築にDockerを使うと便利です。
Nodejsの環境にはnode公式のコンテナの8.14.0-alpineを利用しました。

docker インストール for mac
docker インストール for windows
dockerhub nodejs

Token取得用のDockerfile

FROM node:8.14.0-alpine

COPY src /node/src

WORKDIR /node/src

RUN npm install

CMD node /node/src/get_token.js

フォルダ構成

$ tree .
#.
# ├── Dockerfile
# └── src
#    ├── get_token.js
#    └── package.json

Docker コンテナの実行

$ docker build -t token-test:1.0 .
$ docker run --rm token-test:1.0

これで目的のtokenの取得ができます。
コンテナ上で作業したい場合は

$ docker run --rm -it token-test:1.0 /bin/sh 

を実行するとshでコンテナにログインできます。

Dockerについての参考

https://qiita.com/rawHam/items/80ba13d2d2d56dba411e
https://qiita.com/yuki_ycino/items/b94ae2bf7d78685cd6f5

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away