[AWS] API GatewayとLambdaでOAuth

API GatewayとLambdaを使う際に、FacebookやTwitterなどのOAuth Acesss Tokenをチェックしたいケースがあるかと思います。

この記事では、API GatewayのAuthorizer機能を用いてAccess Tokenを検証する流れを簡略化してまとめてみます。

## 前提

- AWSのブラウザコンソールを利用
- LambdaではNode.jsを利用
- 各OAuthプロバイダ固有処理やエラー処理は割愛

## 処理の流れ

クライアント, SNS, API Gatewayを含めた全体の流れは以下のようなものです。

```
クライアントアプリ(iOS/Android/SPAなど)
--[認証認可情報]--> SNSのAccess Token発行API
--[Access Token]--> クライアントアプリ
--[Access Token]--> API GatewayのMethod Request
--[Access Token]--> Authorizer (=Lambdaの関数) 
--[Access Token]--> SNSのAccess Token検証API 
--[検証結果]--> Authorizer 
----> API GatewayのIntegration Request 
----> バックエンド(LambdaやHTTPリクエストなど)
----> API GatewayのIntegration Response
----> API GatewayのMethod Response
----> クライアントアプリ
```

## 1. [API Gateway] APIの作成 

説明を簡単にするためにMock APIを作成します。

1. 新たなAPIを作成します。
 	- APIs > Create APIをクリック
 
2. 作成したAPIにHTTP Methodを追加します。
	- API > Actions > Create Method > GET > Mock
		- 簡略化のためここではとりあえずルートパスの/を利用します。

3. Mock用のレスポンスJSONを作成します。
	- Integration Response > 200 > Body Mapping Templates > application/json > レスポンス用のJSONを入力

4. APIをデプロイします。
	- API > Actions > Deploy API > Stage作成("prod"とする) 

5. APIを呼び出します。
	- 表示されたURLをcurlなどから叩いて入力したJSONが帰ってくるか確認します

## 2. [Lambda] Custom Authorizer用のFunction作成

API GatewayのAuthorizer機能で利用するAccess Token検証用関数をLambdaで作成します。

2. Create Lambda Function > Configure function (他のタブは無視)
	- Name : authFunction (任意)
	- Runtime : Node.js 4.3 (例はNode.jsにしますが何でもOK)
	- Lambda function code: 

		```js
		exports.handler = function(event, context) {
		
         //API Gatewayから渡されたAccess Token
		  var token = event.authorizationToken;
 
		  // ToDo : Access Tokenの検証処理
		  //   SNSなどのAccess Token検証APIを叩いて結果を取得します。
		  //   ※検証結果は authorized か unauthorized と簡略化します。
		  //   ※通常非同期処理のはずですが簡略化します。
		  // var authResult = 検証結果 'authorized' or 'unauthorized'
		  
		  switch (authResult) {
		    case 'authorized':
		      context.succeed(generatePolicy('user', 'Allow', event.methodArn));
		      break;
		    case 'unauthorized':
		      context.succeed(generatePolicy('user', 'Deny', event.methodArn));
		      break;
		    default:
		      context.fail("Error");
		  }
		};

		var generatePolicy = function(principalId, effect, resource) {
		  return {
		    principalId: principalId,
		    policyDocument: {
		      Version: '2012-10-17',
		      Statement: [{
		        Action: 'execute-api:Invoke',
		        Effect: effect,
		        Resource: resource
		      }]
		    }
		  };
		}
		```
	- Handler : index.handler
	- Role : Create new role
	- Role name : "simple_microservice" (任意)
	- Policy templates : Simple Microservice Template (適当な物を設定)


3. (任意)テスト用のレスポンスを作成します。
	- Actions > Configure Test Event > 以下を貼付
	
	```js
	{
  		"authorizationToken": "authorized"
	}
	```	

4. (任意)テストを実行します。
	- Testをクリックして以下が帰ってくることを確認

	```js
	{
	  "principalId": "user",
	  "policyDocument": {
	    "Version": "2012-10-17",
	    "Statement": [
	      {
	        "Action": "execute-api:Invoke",
	        "Effect": "Allow"
	      }
	    ]
	  }
	}
	```	

## 3. [API Gateway] Custom Authorizerの割当

API GatewayのAuthorizerにLambdaで作成したAccess Token検証用関数を割当てます。

1. 先ほど作成したAPIを選択します。
2. Custom Authorizerを作成します。
	- Authorizers > New Custom Authorizer
		- Lambda region : authFunctionを作成したリージョンを指定
		- Lambda function : `authFunction`
		- Authorizer name : `myAuth` (任意)
		- Identity token source : `method.request.header.Authorization`
			- HTTPリクエストのヘッダのAuthorizationキーの値をLambdaの`authFunction`のauthorizationTokenに渡します。

3. ダイアログが表示されるのでOKをクリックします。
	- API GatewayのroleにauthFunctionを実行する権限が自動追加されます。

4. (任意)テストを実行します。
	- 画面右下の`Test your authorizer`をクリック
	- Identity tokenに`authorized`などを入力してTestをクリック

		```
		{
		  "Version": "2012-10-17",
		  "Statement": [
		    {
		      "Action": "execute-api:Invoke",
		      "Effect": "Allow",
		      "Resource": "arn:aws:execute-api:ap-northeast-1:xxxxx:yyyyyy/null/GET/"
		    }
		  ]
		}		
		```

5. Lambda側で作成したAccess Token検証用の関数を割り当てます
	- API > Resources > Method > Method Request > Authorization
		- 2で設定した`myAuth`を選択します。

6. APIをデプロイします。

7. APIを実行して結果を確認します。
	- HTTP Headerに`Authorization`キーと値をセットしてAPIを叩きます。


以上