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

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





ステップ1(Javascript)
まずはGoogleのOAuth画面の呼び出し。
Reactの書き方なので、functionらしくないですが・・・ただのfunctionだと思ってソースを見てください。
https://accounts.google.com/o/oauth2/auth に必要なパラメーターをざーっとつけて
window.openする感じです。
ポップアップというか別タブでGoogleのOAuth画面が開きます。
で、window.openしたあとは setTimeoutで1秒おきにウィンドウが閉じたかどうかをチェックします。
[bash]
onLoginGoogle: (history) => {
const GOOGLE_CLIENT_ID = “52630050245845-i8xt563tc215pv9mxjy13e2ewm6mrewwl.apps.googleusercontent.com”
let state_token = getRndStr()
Cookies.set(“mystate”, state_token)
let url = “https://accounts.google.com/o/oauth2/auth?”
url += “client_id=” + GOOGLE_CLIENT_ID + “&”
url += “response_type=code&”
url += “scope=email%20profile&”
url += “redirect_uri=http://localhost:3000/oauth_google&”
url += “state=” + state_token
windowLogin = window.open(url)
setTimeout(function(){CheckLoginStatusGoogle(history)}, 1000)
windowLogin.focus()
},
const getRndStr = () => {
ランダムな文字列を生成するメソッドです。
}
[/bash]
ステップ2(Rails)
ステップ1でウィンドウを開くときのURLにコールバック用のURLを設定していますので、Google側の認証が終われば、
コールバックURL(自分で実装するアプリ)に制御が戻ってきます。
テスト環境ですのでRailsのデフォルト http://localhost:3000 でサーバーが立ち上がっていて、
コールバックURLは http://localhost:3000/oauth_google です。
では、Railsの方の実装を見てみましょう。
最初にステップ1でGoogleの認証画面を呼び出した時のステート(ランダム文字列)と
Googleさんが戻してきたステートが一致するか確認します。
OKならば、Developer Consoleで登録した、CLIENT_IDとCLIENT_SECRETを使い、
トークンを取得しに行きます。
最後に得られたトークンを使って、API呼び出しをしてGoogleに登録されている情報を読み出します。
で、cookiesに得られた情報をセットして、このメソッドを抜けます。
cookies[:google_code] = params[:code]
cookies[:email] = email
cookies[:google_uid] = api_result[“user_id”]
[bash]
class OauthController < ApplicationController
#URL /oauth_google への処理
def google
cookies[:google_code] = params[:code]
if cookies[:mystate].present? && cookies[:mystate] == params[:state] &&
params[:code].present?
postData = {
code: params[:code],
client_id: Oauth::GOOGLE_CLIENT_ID,
client_secret: Oauth::GOOGLE_CLIENT_SECRET,
redirect_uri: "http://localhost:3000/oauth_google",
grant_type: "authorization_code"
}
response = Net::HTTP::post_form(URI.parse("https://accounts.google.com/o/oauth2/token"),postData)
result = JSON.parse(response.body)
if result["expires_in"] > 0 && result[“id_token”].present?
api_response = Net::HTTP.get URI.parse(“https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=#{result[‘id_token’]}”)
api_result = JSON.parse(api_response)
if api_result[“issuer”] == “accounts.google.com” &&
api_result[“issued_to”] == Oauth::GOOGLE_CLIENT_ID &&
api_result[“audience”] == Oauth::GOOGLE_CLIENT_ID &&
api_result[“email_verified”]
email = api_result[“email”]
puts “Signin: success Google OAuth ‘#{email}’ / #{api_result[‘user_id’]}”
cookies[:email] = email
cookies[:google_uid] = api_result[“user_id”]
end
end
end
end
end
[/bash]
ステップ3(RailsのJavascript)
OauthControllerのgoogleメソッドで処理をしたので、普通に処理が終われば、ポップアップ画面には
そのメソッドに対応するビューが表示されます。
なので、対応するビューは作るのですが、あくまでダミーで結構です。
そもそも、ポップアップ(別タブ)でGoogle認証が開いていて、認証が終わればポップアップは閉じてほしい
はずですので・・・
で、assets/javascripts 配下に以下のようなJavaScript(CoffeeScriptではない)を作ります。
ファイル名はなんでもいい。
で、ここでする処理は、ダミーのビューが表示された時にそのhrefをチェックして、
認証終了のダミービューだと判定できたら、window.closeしてやるというものです。
[bash]
$(function(){
if (window.location.href.indexOf(“oauth_google”) >0){
window.close();
}
});
[/bash]
ステップ4(SPAのJavascript)
最後にクライアントサイドです。window.openの時にsetTimeoutで呼ばれるメソッドです。
windowLogin.closedの状態を1秒間隔でチェックし、Googleの認証画面が閉じたら
alertに認証で得られた情報をクッキーから表示します。(別に表示する必要はないのですが・・・)
で、認証が通ったと判定できたら、アプリのトップページなどへジャンプしてやります。
あとはSPAなのでAPIサーバーへアクセスするたびにおそらくユーザー認証も必ずすることになると思うので、
APIを呼ぶたびに、ここで得られた情報を付与してやるイメージでしょうか・・・
本題とはそれますが、setTimeoutで引数をつけたfunctionを呼びたいときは 無名関数とする
のがルールのようです。
[bash]
const CheckLoginStatusGoogle = (history) => {
if (windowLogin.closed) {
alert(Cookies.get(‘mystate’) + “\n” + Cookies.get(‘google_code’) + “\n”
+ Cookies.get(‘email’) + “\n” + Cookies.get(‘google_uid’))
if (Cookies.get(‘google_code’) != undefined && Cookies.get(‘email’) != undefined){
history.push(“/counter”)
}
else{
alert(“認証に失敗しました”)
}
}
else{
setTimeout(function(){CheckLoginStatusGoogle(history)}, 1000)
}
}
[/bash]

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