はじめに
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秒おきにウィンドウが閉じたかどうかをチェックします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
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 = () => { ランダムな文字列を生成するメソッドです。 } |
ステップ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”]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
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 |
ステップ3(RailsのJavascript)
OauthControllerのgoogleメソッドで処理をしたので、普通に処理が終われば、ポップアップ画面には
そのメソッドに対応するビューが表示されます。
なので、対応するビューは作るのですが、あくまでダミーで結構です。
そもそも、ポップアップ(別タブ)でGoogle認証が開いていて、認証が終わればポップアップは閉じてほしい
はずですので・・・
で、assets/javascripts 配下に以下のようなJavaScript(CoffeeScriptではない)を作ります。
ファイル名はなんでもいい。
で、ここでする処理は、ダミーのビューが表示された時にそのhrefをチェックして、
認証終了のダミービューだと判定できたら、window.closeしてやるというものです。
|
$(function(){ if (window.location.href.indexOf("oauth_google") >0){ window.close(); } }); |
ステップ4(SPAのJavascript)
最後にクライアントサイドです。window.openの時にsetTimeoutで呼ばれるメソッドです。
windowLogin.closedの状態を1秒間隔でチェックし、Googleの認証画面が閉じたら
alertに認証で得られた情報をクッキーから表示します。(別に表示する必要はないのですが・・・)
で、認証が通ったと判定できたら、アプリのトップページなどへジャンプしてやります。
あとはSPAなのでAPIサーバーへアクセスするたびにおそらくユーザー認証も必ずすることになると思うので、
APIを呼ぶたびに、ここで得られた情報を付与してやるイメージでしょうか・・・
本題とはそれますが、setTimeoutで引数をつけたfunctionを呼びたいときは 無名関数とする
のがルールのようです。
|
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) } } |

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