[JAWS] AWSMを使って認証・認可APIを作る

jaws_icon

モバイルアプリサービス部の五十嵐です。

10/30に弊社で行われた『モバイルバックエンド勉強会 In 秋葉原』で、JAWSについてトークしました。その中でデモとして行った、認証・認可APIの作り方をご紹介します。

[レポート]モバイルバックエンド勉強会 In 秋葉原 #cmdevio | Developers.IO

スライド

デモ

スライドの中のデモで行ったawsm-usersをダウンロードしてデプロイするまでの手順を以下に記載します。

できるもの

awsm-usersを使うことで、認証・認可に必要な最低限のAPIが作成されます。

  • [POST] /users/create
  • [POST] /users/authenticate

  • [GET] /users/list *認可必要

事前準備

Node.js / npm のインストール

JAWSはJavaScriptで書かれており、npmで配布されています。手元の環境にNode.jsとnpmがない場合は、インストールしてください。

参考: nodebrewでNode.jsをバージョン管理 - Node.jsとRubyの環境構築(2) - Qiita

JAWS のインスト−ル

JAWSをnpmでインストールします。

$ npm install jaws-framework -g

プロジェクトの作成

JAWSのプロジェクトを作成します。コマンドなどの詳細はサーバレスアプリケーションフレームワーク JAWS を使ってみる | Developers.IOを参考にしてください。

$ jaws project create
       ____   _____  __      __  _________
      |    | /  _  \/  \    /  \/   _____/
      |    |/  /_\  \   \/\/   /\_____  \
  /\__|    /    |    \        / /        \
  \________\____|__  /\__/\__/ /_________/ v1 (BETA)

       *** The Server-Less Framework ***

JAWS: Enter a project name:  (jaws-E1XgDRPWx) demo
JAWS: Enter a project domain (You can change this at any time:   (myapp.com)
JAWS: Enter an email to use for AWS alarms:  (you@yourapp.com)
JAWS: Enter a stage for this project:  (dev)
JAWS: Select a region for your project:
    us-east-1
    us-west-2
    eu-west-1
  > ap-northeast-1
JAWS: Select an AWS profile for your project:
  > default
JAWS: Creating CloudFormation Stack for your new project (~5 mins)...
JAWS: Preparing your runtime and installing jaws-core module...
sample-app@0.0.1 /Users/igarashi.ryosuke/temp/jaws/sample-app
└─┬ jaws-core-js@0.0.2
  └── dotenv@1.2.0

npm WARN EPACKAGEJSON sample-app@0.0.1 No license field.
JAWS: Your project "sample-app" has been successfully created in the current directory.

$ cd demo

AWSMのダウンロードと組み込み

npm installコマンドでawsm-usersをダウンロードし、jaws postinstallでダウンロードしたnpmをプロジェクトのaws_modulesに追加します。

$ npm install --save awsm-users
$ jaws postinstall awsm-users npm

環境変数の設定

プロジェクトの環境変数を確認すると、設定されていない環境変数が3つあることがわかります。 Lambda自体には環境変数はありませんが、JAWSが環境変数を使えるようにしています。

$ jaws env list dev ap-northeast-1
JAWS: Getting ENV file from S3 bucket: jaws.dev.apnortheast1.myapp-vkdqvcpw.com in ap-northeast-1
JAWS: ENV vars for stage dev:
JAWS: ------------------------------
JAWS: ap-northeast-1
JAWS: ------------------------------
JAWS_STAGE=dev
JAWS_DATA_MODEL_STAGE=dev

JAWS: awsm.json:lambda.envVars and regions where they are used (red means NOT defined in region):
JAWS: ------------------------------
JAWS: USERS_TABLE
JAWS: ------------------------------
JAWS: aws mods using: awsm-users/create/awsm.json,awsm-users/authenticate/awsm.json,awsm-users/list/awsm.json
JAWS: regions: ap-northeast-1

JAWS: ------------------------------
JAWS: JWT_SECRET
JAWS: ------------------------------
JAWS: aws mods using: awsm-users/authenticate/awsm.json
JAWS: regions: ap-northeast-1

JAWS: ------------------------------
JAWS: JWT_ISSUER
JAWS: ------------------------------
JAWS: aws mods using: awsm-users/authenticate/awsm.json
JAWS: regions: ap-northeast-1

awsm-usersに必要な環境変数を設定をします。

$ jaws env set dev ap-northeast-1 USERS_TABLE jaws-users
$ jaws env set dev ap-northeast-1 JWT_SECRET secret
$ jaws env set dev ap-northeast-1 JWT_ISSUER issuer

再び環境変数を確認すると、設定されたことがわかります。

$ jaws env list dev ap-northeast-1
JAWS: Getting ENV file from S3 bucket: jaws.dev.apnortheast1.myapp-vkdqvcpw.com in ap-northeast-1
JAWS: ENV vars for stage dev:
JAWS: ------------------------------
JAWS: ap-northeast-1
JAWS: ------------------------------
JAWS_STAGE=dev
JAWS_DATA_MODEL_STAGE=dev
USERS_TABLE=jaws-users
JWT_SECRET=secret
JWT_ISSUER=issuer


JAWS: awsm.json:lambda.envVars and regions where they are used (red means NOT defined in region):
JAWS: ------------------------------
JAWS: USERS_TABLE
JAWS: ------------------------------
JAWS: aws mods using: awsm-users/list/awsm.json,awsm-users/create/awsm.json,awsm-users/authenticate/awsm.json
JAWS: regions: ap-northeast-1

JAWS: ------------------------------
JAWS: JWT_SECRET
JAWS: ------------------------------
JAWS: aws mods using: awsm-users/authenticate/awsm.json
JAWS: regions: ap-northeast-1

JAWS: ------------------------------
JAWS: JWT_ISSUER
JAWS: ------------------------------
JAWS: aws mods using: awsm-users/authenticate/awsm.json
JAWS: regions: ap-northeast-1

デプロイ

デプロイは2つあります。1つはLambda、API Gateway以外のリソース(ここではDynamoDBなど)のデプロイで、jaws deploy resourcesコマンドで実行できます。

$ jaws deploy resources
JAWS: Resources Deployer  "dev": Deploying resources to region "ap-northeast-1"...
JAWS: Resources Deployer  "dev - ap-northeast-1":  Performing Cloudformation stack update.  This could take a while depending on how many resources you are updating...
JAWS: Resources Deployer  "dev - ap-northeast-1":  Cloudformation stack update completed successfully!

もう一つはLambdaとAPI Gatewayのデプロイで、jaws dashコマンドで実行できます。

$ jaws dash
JAWS: Dashboard for project "sample-app"
 -------------------------------------------
 Project Summary
 -------------------------------------------
    Stages:
       dev ap-northeast-1
    Lambdas: 3
    Endpoints: 3
 -------------------------------------------
 Select Resources To Deploy
 -------------------------------------------
    awsm-users/create
      L) lAwsmUsersCreate
      E) /users/create - POST
    awsm-users/authenticate
      L) lAwsmUsersAuthenticate
      E) /users/authenticate - POST
    awsm-users/list
      L) lAwsmUsersList
      E) /users/list - GET
    - - - - -
  >   Deploy Selected -->
JAWS: -------------------------------------------
JAWS:  Dashboard:  Deploying Lambdas...
JAWS: -------------------------------------------
JAWS: Lambda Deployer:  Packaging "lAwsmUsersList"...
JAWS: Lambda Deployer:  Saving in dist dir /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersList@1445934268684
JAWS: Getting ENV file from S3 bucket: jaws.dev.apnortheast1.myapp-vkdqvcpw.com in ap-northeast-1
JAWS: Lambda Deployer:  Bundled file written to /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersList@1445934268684/bundled.js
JAWS: Lambda Deployer:  Minified file written to /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersList@1445934268684/minified.js
JAWS: Lambda Deployer:  Compressed lambda written to /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersList@1445934268684/package.zip
JAWS: Lambda Deployer:  Uploading lAwsmUsersList to jaws.dev.apnortheast1.myapp-vkdqvcpw.com
JAWS: Lambda Deployer:  Packaging "lAwsmUsersAuthenticate"...
JAWS: Lambda Deployer:  Saving in dist dir /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersAuthenticate@1445934276325
JAWS: Getting ENV file from S3 bucket: jaws.dev.apnortheast1.myapp-vkdqvcpw.com in ap-northeast-1
JAWS: Lambda Deployer:  Bundled file written to /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersAuthenticate@1445934276325/bundled.js
JAWS: Lambda Deployer:  Minified file written to /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersAuthenticate@1445934276325/minified.js
JAWS: Lambda Deployer:  Compressed lambda written to /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersAuthenticate@1445934276325/package.zip
JAWS: Lambda Deployer:  Uploading lAwsmUsersAuthenticate to jaws.dev.apnortheast1.myapp-vkdqvcpw.com
JAWS: Lambda Deployer:  Packaging "lAwsmUsersCreate"...
JAWS: Lambda Deployer:  Saving in dist dir /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersCreate@1445934279765
JAWS: Getting ENV file from S3 bucket: jaws.dev.apnortheast1.myapp-vkdqvcpw.com in ap-northeast-1
JAWS: Lambda Deployer:  Bundled file written to /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersCreate@1445934279765/bundled.js
JAWS: Lambda Deployer:  Minified file written to /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersCreate@1445934279765/minified.js
JAWS: Lambda Deployer:  Compressed lambda written to /var/folders/zz/lwbnbx41153d7k9cwy_y3f4r0000gp/T/lAwsmUsersCreate@1445934279765/package.zip
JAWS: Lambda Deployer:  Uploading lAwsmUsersCreate to jaws.dev.apnortheast1.myapp-vkdqvcpw.com
JAWS: Running CloudFormation lambda deploy...
JAWS: Lambda Deployer:  Done deploying lambdas in ap-northeast-1
JAWS: Lambda Deployer:  Successfully deployed lambdas to the requested regions!
JAWS: -------------------------------------------
JAWS:  Dashboard:  Deploying Endpoints...
JAWS: -------------------------------------------
JAWS: Endpoint Deployer:  Deploying endpoint(s) to region "ap-northeast-1"...
JAWS: Endpoint Deployer:  "dev - ap-northeast-1": found 3 endpoints to deploy
JAWS: Endpoint Deployer:  "dev - ap-northeast-1": created a new REST API on AWS API Gateway with ID: fmeeah15nk
JAWS: Endpoint Deployer:  "dev - ap-northeast-1": found 1 existing resources on API Gateway
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/authenticate": created resource: users
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/authenticate": created resource: authenticate
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/authenticate": created method: POST
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/authenticate": created integration with the type: AWS
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/authenticate": created new lambda access policy statement
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/authenticate": created method response
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/authenticate": created method response
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/authenticate": created method integration response
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/authenticate": created method integration response
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/create": created resource: create
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/create": created method: POST
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/create": created integration with the type: AWS
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/create": created new lambda access policy statement
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/create": created method response
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/create": created method response
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/create": created method integration response
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/create": created method integration response
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/list": created resource: list
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/list": created method: GET
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/list": created integration with the type: AWS
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/list": created new lambda access policy statement
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/list": created method response
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/list": created method response
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/list": created method integration response
JAWS: Endpoint Deployer:  "dev - ap-northeast-1 - users/list": created method integration response
JAWS: Endpoint Deployer:  Endpoints for stage "dev" successfully deployed to API Gateway in the region "ap-northeast-1". Access them @ https://fmeeah15nk.execute-api.ap-northeast-1.amazonaws.com/dev/
JAWS: -------------------------------------------
JAWS:  Dashboard:  Deployments Completed
JAWS: -------------------------------------------

動作確認

Create

<endpoint>/users/createにemailとpasswordをPOSTしてユーザを作成します。DynamoDBのjaws-usersテーブルを確認すると、レコードが1行作成されていることがわかります。

$ curl -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{
    "email": "jacob@jaws.com",
    "password": "password"
}' 'https://9ah96mtt80.execute-api.ap-northeast-1.amazonaws.com/dev/users/create'

{}%

Authenticate

<endpoint>/users/authenticateに先ほど登録したemailとpasswordをPOSTして作成したユーザで認証をします。認証が成功するとjwt(JSON Web Token)が返されます。

$ curl -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{
    "email": "jacob@jaws.com",
    "password": "password"
}' 'https://9ah96mtt80.execute-api.ap-northeast-1.amazonaws.com/dev/users/authenticate' | jq .

{"status":201,"jwt":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJ1XzBlZTM0ZjkwLTdjODktMTFlNS04MTcxLTZiMjAyMWEwNDIyMCIsImlhdCI6MTQ0NTkzNjQ0OCwiZXhwIjoxNDQ1OTM3MDQ4LCJpc3MiOiJpc3N1ZXIifQ.-6kceqqztCpjX4JPFeJUe5WZF-_RAqSjNntI9zQzDzM"}%

List

<endpoint>/users/listにAuthorization: jwtを付与してGETすることで、認可され情報を取得することができます。

$ curl -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJ1XzBiN2U1YTAwLTdlZjctMTFlNS1iNmE0LTIzZGIwNWZjZTlkZSIsImlhdCI6MTQ0NjIwMzU2NSwiZXhwIjoxNDQ2MjA0MTY1LCJpc3MiOiJpc3N1ZXIifQ.nmY7fwrA2XTlmOI7o_0EXoiDFMMaFlmZlvq-gPpr3Lk" "https://9ah96mtt80.execute-api.ap-northeast-1.amazonaws.com/dev/users/list" | jq .

(以下略)

まとめ

JAWSを使ったユーザ認証・認可のAPIを紹介しました。JAWSにはまだまだ少ないですがこのような機能を標準化したパッケージがいくつかあります。これらを雛形にしてアプリケーションを作ることで、開発の初速度をアップすることができますね。あと個人的な注目ポイントは環境変数が使えることです。AWS Lambda自体には環境変数はまだサポートされていませんが、JAWSがサポートしてくれています。アプリケーションを作るうえではコードに埋め込みたくない設定値などが必ず出てくるのですごく助かります。