22
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

Rails 6.1対応版: APIモードのRailsに対してCrossOriginなSPAからSession認証する方法

本記事では、APIモードのRailsに対してCrossOriginなSPAからSession(Cookie)認証する方法を解説します。

モダンなフロントエンド開発だと、Auth0やFirebaseを使った認証例が多く見られますが、バックエンドにRailsを使った認証例はまだまだ少ないと感じています。
JWT認証ではなくCookie認証となると、その数はさらに少ないようです。

実際にやってみるといろいろな障壁があることがわかったので、やるべきことをまとめることにしました。

背景

Next.jsアプリをVercelに、データ永続化とユーザ認証を目的としたAPIモードのRailsアプリをHerokuにデプロイしています。
この状況でNext.jsアプリからRailsに対して認証を試みると、CrossOriginなリクエストであるが故に様々な障壁があります。

Untitled_Portfolio_Site_-_Cacoo.png

  • 本記事に記載する内容はバックエンド側がメインです。(フロントエンド側の実装は需要があれば追々記載予定)
  • Railsの認証はdeviseを使用しています
    • シンプルなCookieを使ったSession認証なので、本記事に記載している内容はdeviseを使っていなくても同様に実践できる想定です。
  • Next.jsではCSRのみを使用しています(SSR, SSG, ISRは使用していない)
    • Next.jsに特化した記載は無いので、本記事に記載している内容はNext.jsを使っていなくても同様に実践できる想定です。

やるべきこと

クライアントからのリクエスト時にCookieを送信する

RailsのSessionストレージにCookieを使う前提なので、フロントエンドからのリクエスト時にCookieを送信するよう設定します。
axiosを使っている場合は、以下のように withCredentials: true を指定することで、Cookieを送信できます。

// 一部のリクエストでCookieを送信する
import axios from 'axios'

const res = await axios.get(
  currentUserPath,
  { withCredentials: true } // このオプションを追加する
)

// 全てのリクエストでCookieを送信する
import axiosBase from 'axios'

const axios = axiosBase.create({
  baseURL: process.env.NEXT_PUBLIC_API_SERVER,
  headers: {
    'Content-Type': 'application/json',
    'X-Requested-With': 'XMLHttpRequest',
  },
  withCredentials: true, // このオプションを追加する
})

const res = await axios.get(currentUserPath)

RailsにてCORS設定する

Gemfileに rack-cors を追加します。
rails new したタイミングでコメントアウトされていることが多いので、コメントを外してください。

Gemfile
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'  # コメントを外す

Gemfileを更新したら bundle install しておきましょう。

$ bundle install

開発環境用のCORS設定を記述します。

config/environments/development.rb
require 'active_support/core_ext/integer/time'

Rails.application.configure do
  # ・・・省略・・・

  # cors
  config.middleware.insert_before 0, Rack::Cors do
    allow do
      origins 'http://localhost:3001'  # リクエスト元となるOriginsを記載する
      resource '*',
               headers: :any,
               methods: %i[get post put patch delete options head],
               credentials: true  # trueとすること
    end
  end
end

origins にはCORSを許可するオリジン(≒URL)を記載してください。
今回はNext.jsの開発環境サーバが http://localhost:3001 だったため、そのオリジンのみ許可しています。
ワイルドカード * とすることもできますが、セキュリティリスクを最小限とするためにも、許可するオリジンは最小限としたほうが良いです。

headersmethods は必要に応じて見直してください。
credentials は レスポンスに Access-Control-Allow-Credentials ヘッダを付与するため、 trueとする必要があります。

参考: RailsでAPIにCORSを設定する

RailsにてCookie, CookieStoreを使えるようにする

APIモードのRailsでは、RackにてCookieやCookieStoreが有効化されていません。
次のように記述することで、これらを有効化します。

config/application.rb
module SampleApplication
  class Application < Rails::Application
    # ・・・省略・・・

    # Cookies
    config.middleware.use ActionDispatch::Cookies               # 追加する
    config.middleware.use ActionDispatch::Session::CookieStore  # 追加する
  end
end

参考: Rails の API モードでセッションやクッキーを使えるようにする

CookieのSameSite属性をNone, Secure属性をtrueにする

RailsからのレスポンスヘッダーのSameSite属性はデフォルト Lax となっています。
CrossOriginでCookieをやり取りするためにはこれを None とする必要があります。

rails_same_site_cookie というgemをインストールすることで、SameSite属性を None かつ Secure属性を true にします。

Gemfile
gem "rails_same_site_cookie", "~> 0.1.8"  # 追加する

Gemfileを更新したら bundle install しておきましょう。

$ bundle install

参考: 【Rails】SameSiteとSecure属性の付与〜Railsのセキュリティ対策〜

しかし、私の環境ではこのGemをインストールしただけではSameSite属性が None となりませんでした。
これはRails 6.1で追加された下記オプションが影響しているようです。

rails6.1で加わった新しいアプリケーション設定であるnew_framework_defaults_6_1.rbの各項目をさらっと解説

このため、application.rb に設定を追記することで、SameSite属性を None とします。

config/application.rb
module SampleApplication
  class Application < Rails::Application
    # ・・・省略・・・

    # Cookies
    config.middleware.use ActionDispatch::Cookies
    config.middleware.use ActionDispatch::Session::CookieStore
    config.action_dispatch.cookies_same_site_protection = :none  # 追加する
  end
end

先の手順で追加した rails_same_site_cookie はSecure属性を true とする役割を担っているので、アンインストールせずに残しておきます。

まとめ

以上の方法でCrossOriginなSPAからAPIモードのRailsに対してSession認証ができるようになりました。

プロトコルもフレームワークもブラウザも、セキュリティ強化の一環で制約をかける方向に進化しているが故、一昔前に比べると随分と考慮すべきことが増えたなあという印象です。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
22
Help us understand the problem. What are the problem?