19
17

More than 3 years have passed since last update.

AmazonAPIGatewayのLambdaAuthorizer(Node.js)でJWTを検証する(LambdaをServerlessFrameworkでデプロイする)

Posted at

はじめに

AWSでAPIを構築する際、必ずといっていいほど登場するのがAmazonAPIGatewayですよね。
APIGatewayの認証ってどうされてますか?

  • 別に必要ないからしてない
  • APIKeyを使っている
  • CognitoをAuthorizerにしてトークンの検証をする
  • LambdaAuthorizerでトークンの検証をする

など色々なパターンがあると思います。今回は最後に書いた「LambdaAuthorizerでトークンの検証をする」について説明して、その構成をServerlessFrameworkでデプロイするところまでを書いていきます。

登場人物

JSON Web Token (JWT)

JSONをベースとしたトークン。
「ヘッダー / ペイロード / 署名」といった3つのセクションから構成されていて、署名はヘッダーとペイロードのハッシュされた組み合わせです。
hogehoge.foofoofoo.barbarbarbar のような文字列です。

ServerlessFramework

Serverlessなアーキテクチャを構築するためのフレームワーク。AWSだけじゃなくAzureやGCPなど他のクラウドサービスでも利用することができる。
詳しくは 公式ページ で確認してみてください。

構成

image.png

Lambda Authorizer

JWTの検証をLambda内で行っていきます。
今回はNode.js(TypeScript)での例を書いています。

authorizer.ts
import { Handler, Context, Callback, CustomAuthorizerEvent } from "aws-lambda"
import * as jsonwebtoken from "jsonwebtoken"
import jwkToPem from "jwk-to-pem"
import jwk from "./jwk"
const pem = jwkToPem(jwk as any)

export const handler: Handler = async (
  event: CustomAuthorizerEvent,
  _context: Context,
  callback: Callback
): Promise<any> => {
  console.log(JSON.stringify(event))

  jsonwebtoken.verify(
    event.authorizationToken,
    pem,
    {
      algorithms: ["RS256"]
    },
    (err: jsonwebtoken.VerifyErrors, decodedToken: object | string) => {
      if (err) {
        callback(null, {
          principalId: 1,
          policyDocument: {
            Version: "2012-10-17",
            Statement: [
              {
                Action: "execute-api:Invoke",
                Effect: "Deny",
                Resource: event.methodArn
              }
            ]
          },
          context: {
            messagge: "Custom Error Message"
          }
        })
      } else {
        console.log(decodedToken)
        callback(null, {
          principalId: 1,
          policyDocument: {
            Version: "2012-10-17",
            Statement: [
              {
                Action: "execute-api:Invoke",
                Effect: "Allow",
                Resource: event.methodArn
              }
            ]
          },
          context: {
            messagge: "Custom Allow Message"
          }
        })
      }
    }
  )
}

解説

JWT検証で使うモジュールをインストール

$ yarn add jsonwebtoken jwk-to-pem
$ yarn add -D @types/jsonwebtoken @types/jwk-to-pem

jsonwebtoken がJWTトークンの検証をするライブラリです。
jwt-to-pem はJWTトークンを検証するための JSON Web Key (JWK) をpem形式に変換するライブラリです。

JWT検証

jsonwebtokenverify というメソッドを使って検証をします。

jsonwebtoken.verify(
  event.authorizationToken, //  requestHeader内のToken
  pem, // JWKから生成したpem
  { algorithms: ["RS256"] },
  (err: jsonwebtoken.VerifyErrors, decodedToken: object | string) =>  {}
)

( Cognitoを使っている場合のJWK取得 )

Cognitoが発行したJWTの検証に使うJWKは https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json から取得する事ができます。
そのJSONを何かしらの方法でimportしてください。今回の例はTSファイルにしてimportしています。
また上記のURLからJWKを取得すると2つKeyが入っていますが、最初の一つを利用すればいいです。

jwk.ts
export default {
  alg: "RS256",
  e: "XXXX",
  kid: "xxxxxxxxxxxxxx",
  kty: "RSA",
  n: "xxxxxxxxxxxxxx",
  use: "sig"
}

検証後の処理

CallbackでAPIGatewayにレスポンスを返して行きます。
Statement内のEffectが AllowDeny かでその後の処理を判断させます。

// 成功時
callback(null, {
  principalId: 1,
  policyDocument: {
    Version: "2012-10-17",
    Statement: [
      {
        Action: "execute-api:Invoke",
        Effect: "Allow",
        Resource: event.methodArn
      }
    ]
  },
  context: {
    messagge: "Custom Allow Message"
  }
})

// 失敗時
callback(null, {
  principalId: 1,
  policyDocument: {
    Version: "2012-10-17",
    Statement: [
      {
        Action: "execute-api:Invoke",
        Effect: "Deny",
        Resource: event.methodArn
      }
    ]
  },
  context: {
    messagge: "Custom Error Message"
  }
})

ServerlessFrameworkでデプロイする

プロジェクト作成

LambdaのソースコードをTypeScriptで書いているので、TypeScript用のテンプレートを使ってプロジェクトを作成します。

$ npx -p serverless sls create -t aws-nodejs-typescript -p example-serverless-ts

serverless.yml

AuthorizerをつけたいAPIGatewayをイベントとするfunctionの中に authorizer を追加していきます。

  • name はAuthorizerとなるLambdaです。同じyml内で定義している場合はこの name で参照することができます。外部でデプロイされているLambdaをAuthorizerにしたい場合は、 arn に対してそのLambdaのARNを指定します。
  • identitySource はTokenをどこを参照すれば習得できるのかという設定です。この場合はリクエストヘッダー内の Authorization を参照するようになっています。
  • type はAuthorizerとなるLambdaのeventに渡される値の設定です。単純にTokenを取得したいだけなら token がおすすめです。リクエストの内容をすべて取得したい場合は request を指定します。
authorizer:
  name: authorizer
  identitySource: method.request.header.Authorization
  type: token

全体としてはこんな感じになります。

serverless.yml
service:
  name: lambdda-node-ts

plugins:
  - serverless-webpack

provider:
  name: aws
  runtime: nodejs10.x
  region: ap-northeast-1

functions:
  authorizer:
    handler: authorizer.handler
  index:
    handler: index.handler
    events:
      - http:
          method: get
          path: hello
          cors: true
          authorizer:
            name: authorizer
            identitySource: method.request.header.Authorization
            type: token

あとは sls deploy でデプロイすれば作業は完了です。

さいごに

今回はLambdaAuthorizerを紹介しました。
トークンの検証にLamdbaを使うことで、トークンの検証以外にもAPIに対するリクエストのログを取ることができたり、怪しいリクエストが来た場合にメール送信などカスタマイズすることができます。
Lambdaを挟むことでかなりできることの幅が広がります!
ではまた!!

19
17
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
19
17