AWS SDK for Ruby V2のCognitoIdentity::Clientクラスを使ってみました。
サンプルコード
logins = {
'graph.facebook.com' => 'facebook_oauth2_access_token',
'api.twitter.com' => ['twitter_user_token', 'twitter_user_secret'].join(';')
}
cognito = Aws::CognitoIdentity::Client.new(region: 'us-east-1')
resp = cognito.get_id(
identity_pool_id: 'us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX',
logins: logins
)
pp resp.identity_id # => "us-east-1:YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY"
説明
まず、Cognitoを使うとき、FacebookやTwitterのアクセストークンの取得操作は、自前で行う必要があります。(Amazon Cognito側ではない)
利用する際は事前に取得したアクセストークンをCognitoに渡すことで、Cognitoの側でユーザーID等を取得して、Cognito IDを割り当ててくれます。
その際に、複数の認証プロバイダのトークンをハッシュで渡すことで、それらを1つのCognito IDに割り当てる処理を行ってくれます。
アプリケーション側ではこのCognito IDを使ってユーザーを識別することで、簡単に名寄せを実現できます。
プロバイダトークンのハッシュを準備
logins = {
'graph.facebook.com' => 'facebook_oauth2_access_token',
'api.twitter.com' => ['twitter_user_token', 'twitter_user_secret'].join(';')
}
'プロバイダ' => 'トークン'
のハッシュを作ります。ドキュメントに記載がありますが、TwitterについてはAPIから得られるtoken
とsecret
を;
でつないだ文字列を使う必要がある点に注意して下さい。
Aws::CognitoIdentity::Clientインスタンスを作る
cognito = Aws::CognitoIdentity::Client.new(region: 'us-east-1')
Aws::CognitoIdentity::Client#get_idでCognito IDを得る
resp = cognito.get_id(
identity_pool_id: 'us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX',
logins: logins
)
pp resp.identity_id # => "us-east-1:YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY"
事前に作成しておいたIdentity PoolのID(REGION:GUID形式)と、先ほど作成したプロバイダトークンのハッシュを渡します。
応答の#identity_id
でREGION:GUID形式のCognitoIDを得られました。
なお、このAPIは公開APIなので、AWSのアクセストークンは不要とのこと。
Rails + Omniauthで実験したコード
以下、実際に実験したコードの一部を張っておきます。
class AuthController < ApplicationController
# GET /auth/:provider/callback
def callback
# current_user.logins
# ログイン中ならユーザーの(有効期限内の)アクセストークンのハッシュを得る、
# 未ログインなら空の配列を用意する
logins = current_user.present? ? current_user.logins : {}
# 今回ログインしたプロバイダのトークンを準備する
case omniauth[:provider]
when 'facebook'
logins['graph.facebook.com'] = omniauth[:credentials][:token]
when 'twitter'
logins['api.twitter.com'] = [omniauth[:credentials][:token], omniauth[:credentials][:secret]].join(';')
else
raise 'Unknown Provider'
end
# Cognito IDの取得し、それをユーザーの識別に使う
cognito = Aws::CognitoIdentity::Client.new(region: 'us-east-1')
resp = cognito.get_id(
identity_pool_id: Rails.application.secrets.aws_cognito_identity_pool_id,
logins: logins,
)
identity_id= resp.identity_id
Rails.logger.debug "Cognito IdentityID: #{identity_id}"
user = User.where(identity: identity_id).first_or_create
# Credential.provide_for(provider_name)
# 認証プロバイダに対応するSTIクラス(TwitterCredentialとかFacebookCredentialとかを返す)
if provider_class = Credential.provide_for(omniauth[:provider])
# ユーザーとアクセストークンを関連付ける。同一プロバイダの古いトークンがあれば削除。
# (アクセストークンはサーバで持って置いて必要な時や名寄せの時に使います)
provider_class.from_user_omniauth(
user: user,
omniauth: omniauth)
else
Rails.logger.error "Unsupported provider: #{omniauth[:provider]}"
end
# ログイン中のユーザーをセッションに入れておく
session[:user_id] = user.id
redirect_to root_url
end
def logout
session.delete(:user_id)
redirect_to root_url
end
private
def omniauth
request.env['omniauth.auth']
end
end
感想
Cognitoを使うことで、割と面倒な名寄せを上手く実装できた気がします。
Tokyoリージョンはありませんが、用途的に頻繁にアクセスするものではないので、個人的には多少のレイテンシの高さは気にならないかなと思っています。