関連記事
こちらの記事の続きです
- Rails 5.1 API mode + webpacker + react + reactstrap で ToDO アプリを書く
- Rails 5.1 API mode + webpacker + react + reactstrap な ToDO アプリに認証機能を追加する (sorcery gem で JWT)
導入手順
- 基本的な手順は公式に記載されているので、その手順に従って導入を進めます
- https://github.com/Sorcery/sorcery/wiki/External
ドキュメントに記載されていないところで、重要なポイントは Rails API mode でプロジェクトを作成した場合には、Cookie と Session Cookie が有効になっていません (公式へのリンク)
Rails を使った方なら分かると思いますが、具体的に言えば session
が使えない、ということになります
ですので、 config/application.rb
で ActionDispatch::Session::CookieStore
を有効にする必要があります。(もちろんですが、本番環境などでは MemcacheStore などを使うようにしましょう)
config/application.rb
# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true
+ config.middleware.use ActionDispatch::Cookies
+ config.middleware.use ActionDispatch::Session::CookieStore
React アプリとの連携
- 意外と悩ましいこととして、認証完了後にどのようにして React アプリ側に JWT トークンを渡すのか、という点があります
- 大雑把に認証の流れを整理すると、以下のようになると思います
- OAuth 認証を行うボタンをクリック
- Twitter のアプリ連携確認画面に遷移
- アプリ連携を twitter の画面から許可
- Rails サーバ側にリダイレクトされる
- Rails サーバで認証処理を行う
- ...
- 整理してみると、上記 6. のタイミングでいい感じに React アプリに JWT トークンが渡せればいい、ということになります
-
Spring Security REST の OAuth で実現している方法を
パクって参考にして、Redirect 時に JWT Token を URL パラメータとして React アプリ側に渡すようにします
サーバ側のコードに示すと以下のようになります。 def callback
が twitter のアプリ連携確認画面から返ってきたときのアクションです
app/controllers/oauths_controller.rb
class OauthsController < ApplicationController
def oauth
login_at(params[:provider])
end
def callback
provider = params[:provider]
if @user = login_from(provider)
# ここで JWT token 発行
tokens = Jwt::TokenProvider.refresh_tokens @user
# React アプリを動かしている URL にリダイレクトする
# http://localhost:3000/#/?access_token=....&refresh_token=... という形でリダイレクトさせる
redirect_to "#{File.join(root_url, '#', '?')}#{tokens.to_query}", notice: "Logged in from #{provider.titleize}"
else
begin
@user = create_from(provider)
reset_session
auto_login(@user)
# ここで JWT token 発行
tokens = Jwt::TokenProvider.refresh_tokens @user
# React アプリを動かしている URL にリダイレクトする
# http://localhost:3000/#/?access_token=....&refresh_token=... という形でリダイレクトさせる
redirect_to "#{File.join(root_url, '#', '?')}#{tokens.to_query}", :notice => "Logged in from #{provider.titleize}"
rescue
redirect_to root_path, :alert => "Failed to login from #{provider.titleize}"
end
end
end
end
React アプリ側で params を受け取る
- こちらは比較的簡単です
- React Router v4 を使っている場合、
location.search
で取得可能ですので、componentDidMount
のタイミングで取得するようにします - コードの一部を記載すると、以下のようになります
...
let tokens = queryString.parse(get(this, "props.location.search"));
if (!isEmpty(tokens)) {
localStorage.setItem('access_token', tokens.access_token)
}
...
isEmpty
、get
はそれぞれ、lodash/isEmpty
、lodash/get
を利用しています
最後に
- 意外と簡単に OAuth 認証を実現することができました
- 話題が逸れますが、 「OAuth 認証」という単語は微妙らしく、 こんな記事 もあるんですね
- URL パラメータに JWT トークンを渡すのはセキュリティ的にどうなんだろう、と疑問に思って調べてみたところ、こんな記事がありました。