背景
- Twitter OAuthをサーバーレスで作成したかった
やったこと
Cognitoの調査
AWSの認証基盤であるCognitoの調査を行った。
「Cognitoを使えば良い感じにAPI Gatewayの認証/認可が行けるのでは??」と甘く見積もるなどしたけど、結論しんどかった。
Cognito UserPool
API Gatewayがデフォルトで認証/認可に対応している、便利。
名前の通りUserPool機能も備えているので属性情報の格納もできる、凄い。
IDPはFB, Amazon, Google, SAMLのみ対応。
Google認証の実体はOpenID ConnectのようなのでLINEとかも行けるっぽい
Twitterに未対応だったため見送り。Twitter対応してる&Federated Identity同等のIDPの自由度があれば最高だった。。。
Cognit Federated Identity
所謂認可用トークンの発行しか行わない模様。
認証用IDの発行もするが、IAMと連携させないとうまい具合に認証ができないのが難点。正直認証の実装がまだ見えていない。(属性情報のベストプラクティスはやっぱりDynamoかな。ここだけUserPoolの機能借りたいしたい)
また、API GatewayはFederated Identityで発行するstsトークンでアクセスを行う。stsで認可を行うため結構癖が強く扱いづらい...
httpの署名はAPI GatewayサービスでSDKが生成されるが汎用性が低く使いづらい。野良のライブラリ探すか汎化させるのが吉。
IDPは複数対応しており、Twitterにも対応されている。なのでこちらを採用。
API Gatewayの調査
認証/認可の方法は3(+1)パターン
1. Cognit UserPool
Cognito UserPoolの場合、API Gatewayがデフォルトで認証/認可に対応している。
httpへの署名はAuthenticationヘッダーにjwtを仕込むだけなのでテストも楽。
今回はUserPoolを使用しないため見送り
2. API Gatewayの前段にLambdaを設定する
API Gatewayの前段に認可用の自前Lambdaを挟む事が可能。
3. IAMでの認可
パス単位でLambdaの認可を要求することが可能
Cognito Federated Identityのstsで行けるのでは?と思いこれを採用
4. 実行されたLambdaの中で認証/認可処理を行う
所謂自前実装。
最高の自由度が手に入る。
実装する
githubは↓
https://github.com/y-ohgi/serverless-practice/tree/master/twitter/api
Cognito Federated Identityの用意
Twitterからコンシューマートークンを取得し、Cognito Federated Identityに食わせる。以上。
serverless.ymlの記述
functions: auth_twitter: handler: src/functions/auth/twitter.auth environment: TWITTER_CONSUMER_KEY: ${self:custom.otherfile.environment.${self:provider.stage}.TWITTER_CONSUMER_KEY} TWITTER_CONSUMER_SECRET: ${self:custom.otherfile.environment.${self:provider.stage}.TWITTER_CONSUMER_SECRET} events: - http: path: auth method: get auth_callback: handler: src/functions/auth/twitter.callback environment: TWITTER_CONSUMER_KEY: ${self:custom.otherfile.environment.${self:provider.stage}.TWITTER_CONSUMER_KEY} TWITTER_CONSUMER_SECRET: ${self:custom.otherfile.environment.${self:provider.stage}.TWITTER_CONSUMER_SECRET} TWITTER_CLIENT_REDIRECT_URL: ${self:custom.otherfile.environment.${self:provider.stage}.TWITTER_CLIENT_REDIRECT_URL} events: - http: path: auth/callback method: get
主な登場人物は auth_twitter
と auth_callback
の2つのfunction。
auth_twitter
ではTwitterのログインURLの発行・リダイレクトを行う。
auth_callback
ではTwitterからのリクエスト受け取り・アクセストークン発行・クライアントへのリダイレクトを行う。
リダイレクトはLambdaプロキシ統合を使用して行う。
hello: handler: handler.hello events: - http: path: hello method: get authorizer: aws_iam
認可は authorizer
プロパティに aws_iam
を指定し、IAMでの認可させる。
Lambdaの実装
npmの ciaranj/node-oauth を用いて実行する。
auth_twitter
Twitterのログイン画面へ飛ばす。
リダイレクト処理は先述したLambdaプロキシ統合を選択していればヘッダーとHTTPステータスコードはコードで表現できる(プロキシ統合OFFだとヘッダーをいじるためにGUI上でポチポチが...)
const auth = (event, context, callback) => { const oa = new OAuth( 'https://api.twitter.com/oauth/request_token', 'https://api.twitter.com/oauth/access_token', ENV.TWITTER_CONSUMER_KEY, ENV.TWITTER_CONSUMER_SECRET, '1.0', 'https://' + event.headers.Host + '/' + event.requestContext.stage + '/auth/callback', 'HMAC-SHA1' ) oa.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, results){ if (error) { callback(error, null) } else { callback(null, { statusCode: 301, headers: { 'Location': 'https://twitter.com/oauth/authenticate?oauth_token='+oauth_token, 'Set-Cookie': 'oauth_token_secret=' + oauth_token_secret }, body: JSON.stringify({ 'message': 'redirect...' }) }) } }) }
auth_callback
Twitterから帰ってきたリクエストを受け取り、アクセストークンを発行し、クライアントへリダイレクトさせる
const callback = (event, context, callback) => { const oa = new OAuth( 'https://api.twitter.com/oauth/request_token', 'https://api.twitter.com/oauth/access_token', ENV.TWITTER_CONSUMER_KEY, ENV.TWITTER_CONSUMER_SECRET, '1.0', null, 'HMAC-SHA1' ) oa.getOAuthAccessToken( event.queryStringParameters.oauth_token, Cookie.parse(event.headers.Cookie).oauth_token_secret, event.queryStringParameters.oauth_verifier, function(error, access_token, access_token_secret, results) { if (error) { callback(error, null) } else { const clienturl = ENV.TWITTER_CLIENT_REDIRECT_URL + '?access_token=' + access_token + '&access_token_secret=' + access_token_secret callback(null, { statusCode: 301, headers: { 'Location': clienturl }, body: JSON.stringify({ 'message': 'redirect...' }) }) } } ) }
参考
serverlessでtwitter認証を作ってみた - Qiita
所感
マネージドサービス使っているはずが自前実装の箇所が多すぎて自前認証基盤作ったほうが後々幸せなんじゃないのだろうかと思う。
たぶん、UserPoolで対応されているIDPのみ or メアド&パスワードで要件が満たせる案件であれば(イマドキなかなか難しそうな気はするけど...)、UserPool使うのがAWSでサーバレススタック使う上で一番楽なのかなと。
今回はフロントがWebだったので、モバイルアプリで作るとまた違うのかもしれないー?