元フリーエンジニアライフ

Ruby on Rails とか MovableType とかAWSやってるフリーランスウェブエンジニアの記録でした。現在は法人成りしてIT社長。

Amazon Cognito Identity でFacebookやTwitterログインの名寄せをしてみた

この記事はQiitaに書いた奴のコピーです。


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を使うとき、FacebookTwitterのアクセストークンの取得操作は、自前で行う必要があります。(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から得られるtokensecret;でつないだ文字列を使う必要がある点に注意して下さい。

Aws::CognitoIdentity::Clientインスタンスを作る

http://docs.aws.amazon.com/sdkforruby/api/Aws/CognitoIdentity/Client.html#initialize-instance_method

cognito = Aws::CognitoIdentity::Client.new(region: 'us-east-1')

Aws::CognitoIdentity::Client#get_idでCognito IDを得る

http://docs.aws.amazon.com/sdkforruby/api/Aws/CognitoIdentity/Client.html#get_id-instance_method

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リージョンはありませんが、用途的に頻繁にアクセスするものではないので、個人的には多少のレイテンシの高さは気にならないかなと思っています。