はじめに
ReactでSPAを作ることになって、ログイン認証にメール&パスワードの認証に加えて、
GoogleやTwitter認証も加えたくなった時、どうすればよいかわからなかった。
バックエンドはRailsだが、あくまでAPIサーバー的な立場なので メジャーなomniauth
なんかは使えそうにない。。。
前回のGoogle編に続き、今回はTwitter編。
あれこれ悩みながらなんとか動くところまでたどり着いたので、やり方を公開。
React+Reduxの環境なんですが、そこまで立ち入ると話がややこしくなるので、
実装のエキス部分だけを記載してみます。
Twitter Developerにアプリ登録
Twitter Developer
これについては上記のURLにアクセスしてアプリを登録してください。
アプリ名とコールバックURLを登録したら、Consumer Key (API Key)とConsumer Secret (API Secret)がもらえます。
詳しくは書きません、ググればこれはたくさん書いてあります。

動作イメージ
デザインはしょぼいですが、こんな感じのログイン画面。
GoogleかTwitterで認証してログインしてね、というもの。
今回はTwitterについてのみ取り上げます。
Twitterでログインをクリックすると、Twitterが用意しているOAuth用の画面が表示されて、
Twitterのユーザー名とパスワードを入れて、最後に今回作るアプリ(SPA)へのアクセス許可画面が表示され、
許可すると、SPAアプリにログイン出来るというもの。




ステップ1(Javascript、Ajax)
まずは
まずはTwitterのOAuth用URLの取得のためのRails API(自作)呼び出し。
Reactの書き方なので、functionらしくないですが・・・ただのfunctionだと思ってソースを見てください。
API呼び出しの結果、OAuth用のURLが得られるので、window.openする感じです。
ポップアップというか別タブでTwitterのOAuth画面が開きます。
で、window.openしたあとは setTimeoutで1秒おきにウィンドウが閉じたかどうかをチェックします。
[bash]
onLoginTwitter: (history) => {
axios.get(“http://localhost:3000/get_twitter_oauth_url”, {
withCredentials: true,
params: {}
})
.then(function (response) {
windowLogin = window.open(response.data.oauth_url)
setTimeout(function(){CheckLoginStatusTwitter(history)}, 1000)
windowLogin.focus()
})
.catch(function (response) {
console.log(response);
});
}
[/bash]
ステップ1.5(Rails)
ステップ1から呼び出されるRailsのソース。
CONSUMER_IDとCONSUMER_SECRETを用いて、OAuth用のURLを取得し、JSONで結果を戻します。
[bash]
require ‘oauth’
def get_twitter_oauth_url
consumer = OAuth::Consumer.new(
Oauth::TWITTER_CONSUMER_ID,
Oauth::TWITTER_CONSUMER_SECRET,
{ :site => “https://api.twitter.com” }
)
request_token = consumer.get_request_token(
:oauth_callback => “http://localhost:3000/oauth_twitter”
)
cookies[:request_token] = request_token.token
cookies[:request_token_secret] = request_token.secret
rtn = {}
rtn[“status”] = true
rtn[“oauth_url”] = request_token.authorize_url
render json: rtn
end
[/bash]
ステップ2(Javascript)
ステップ1のソースのWindow呼び出しのところだけを抜粋。
RailsのAPIで戻ってきた認証用URLを引数にして window.open。
[bash]
windowLogin = window.open(response.data.oauth_url)
[/bash]
ステップ3
ここはプログラムする必要なしです。TwitterのOAuthの画面で
純粋に認証&許可をしてもらいます。
ステップ4(Rails)
Twitter側の認証と許可の処理が終わると、ステップ1.5で指定したコールバックURLに制御が戻ってきます。
テスト環境ですのでRailsのデフォルト http://localhost:3000 でサーバーが立ち上がっていて、
コールバックURLは http://localhost:3000/oauth_twitter です。
では、Railsの方の実装を見てみましょう。
最初にCONSUMER_IDとCONSUMER_SECRETを用いて、トークンを作成します。
さらに、パラメーターで渡ってきた、 oauth_tokenとoauth_verifierを使って
アクセストークンを得ます。
最後に得られたアクセストークンを使ってAPIアクセスし、ユーザー情報を得ます。
で、cookiesに得られた情報をセットして、このメソッドを抜けます。
cookies[:oauth_token] = params[:oauth_token]
cookies[:twitter_uid] = user_info[“id”]
cookies[:screen_name] = user_info[“screen_name”]
[bash]
class OauthController < ApplicationController
#URL /oauth_twitter への処理
def twitter
consumer = OAuth::Consumer.new(
Oauth::TWITTER_CONSUMER_ID,
Oauth::TWITTER_CONSUMER_SECRET,
{ :site => “https://api.twitter.com” }
)
request_token = OAuth::RequestToken.new(
consumer,
cookies[:request_token],
cookies[:request_token_secret]
)
access_token = request_token.get_access_token(
{},
:oauth_token => params[:oauth_token],
:oauth_verifier => params[:oauth_verifier]
)
response = consumer.request(
:get,
‘/1.1/account/verify_credentials.json’,
access_token, { :scheme => :query_string }
)
rtn = {}
case response
when Net::HTTPSuccess
user_info = JSON.parse(response.body)
if user_info[“screen_name”]
cookies[:oauth_token] = params[:oauth_token]
cookies[:twitter_uid] = user_info[“id”]
cookies[:screen_name] = user_info[“screen_name”]
else
#”Authentication failed”
end
else
#”Failed to get user info via OAuth”
end
end
end
[/bash]
RailsのGemfileに以下の3つのgemを追加します。
[bash]
gem ‘json’
gem ‘oauth’
gem ‘rack-cors’, :require => ‘rack/cors’
[/bash]
SPAをサーブしているサーバーとAPIをサーブしているサーバーが別物になるので、
クロスドメインなAPIアクセスとなるため、API側でそれを許可する必要があります。
gem rack-cors でそれに対応できます。で、対応するためにapplication.rbを
以下のように書き換えます。 本番ではoriginsは「*」ではなく、ちゃんと特定のドメイン名を入れるべし。
config/application.rb
[bash]
class Application < Rails::Application
config.middleware.insert_before ActionDispatch::Static, Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :delete]
end
end
end
[/bash]
ステップ5(RailsのJavascript)
OauthControllerのtwitterメソッドで処理をしたので、普通に処理が終われば、ポップアップ画面には
そのメソッドに対応するビューが表示されます。
なので、対応するビューは作るのですが、あくまでダミーで結構です。
そもそも、ポップアップ(別タブ)でTwitter認証が開いていて、認証が終わればポップアップは閉じてほしい
はずですので・・・
で、assets/javascripts 配下に以下のようなJavaScript(CoffeeScriptではない)を作ります。
ファイル名はなんでもいい。
で、ここでする処理は、ダミーのビューが表示された時にそのhrefをチェックして、
認証終了のダミービューだと判定できたら、window.closeしてやるというものです。
[bash]
$(function(){
if (window.location.href.indexOf(“oauth_twitter”) >0){
window.close();
}
});
[/bash]
ステップ6(SPAのJavascript)
最後にクライアントサイドです。window.openの時にsetTimeoutで呼ばれるメソッドです。
windowLogin.closedの状態を1秒間隔でチェックし、Twitterの認証画面が閉じたら
alertに認証で得られた情報をクッキーから表示します。(別に表示する必要はないのですが・・・)
で、認証が通ったと判定できたら、アプリのトップページなどへジャンプしてやります。
あとはSPAなのでAPIサーバーへアクセスするたびにおそらくユーザー認証も必ずすることになると思うので、
APIを呼ぶたびに、ここで得られた情報を付与してやるイメージでしょうか・・・
本題とはそれますが、setTimeoutで引数をつけたfunctionを呼びたいときは 無名関数とする
のがルールのようです。
[bash]
const CheckLoginStatusTwitter = (history) => {
if (windowLogin.closed) {
alert(Cookies.get(‘twitter_uid’) + “\n” + Cookies.get(‘screen_name’))
if (Cookies.get(‘twitter_uid’) != undefined && Cookies.get(‘screen_name’) != undefined){
history.push(“/counter”)
}
else{
alert(“認証に失敗しました”)
}
}
else{
setTimeout(function(){CheckLoginStatusTwitter(history)}, 1000)
}
}
[/bash]

あとがき
あっさりと説明しちゃいましたが、理屈がわかるまでも少し時間がかかりました。
SPAでSNS認証するならどうすりゃいいんだという情報が、以外になくて・・・
あちこち読み漁って、なんとか形になりました。
何かの参考になればと・・・・
Leave a comment