Rails 5.1 API mode + webpacker + react + reactstrap な ToDO アプリに Twitter OAuth 認証を追加する(Sorcery gem)

関連記事

こちらの記事の続きです

導入手順

ドキュメントに記載されていないところで、重要なポイントは Rails API mode でプロジェクトを作成した場合には、Cookie と Session Cookie が有効になっていません (公式へのリンク)
Rails を使った方なら分かると思いますが、具体的に言えば session が使えない、ということになります

ですので、 config/application.rbActionDispatch::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 トークンを渡すのか、という点があります
  • 大雑把に認証の流れを整理すると、以下のようになると思います
    1. OAuth 認証を行うボタンをクリック
    2. Twitter のアプリ連携確認画面に遷移
    3. アプリ連携を twitter の画面から許可
    4. Rails サーバ側にリダイレクトされる
    5. Rails サーバで認証処理を行う
    6. ...
  • 整理してみると、上記 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)
    }
...

isEmptyget はそれぞれ、lodash/isEmptylodash/get を利用しています

最後に

  • 意外と簡単に OAuth 認証を実現することができました
    • 話題が逸れますが、 「OAuth 認証」という単語は微妙らしく、 こんな記事 もあるんですね
  • URL パラメータに JWT トークンを渡すのはセキュリティ的にどうなんだろう、と疑問に思って調べてみたところ、こんな記事がありました。
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.