🗿

RailsにAuth0の認証機能を追加、ついでにRenderでデプロイしてみた

2024/05/15に公開

はじめに

久しぶりにRailsで開発をすることとなり、認証機能にAuth0を使用してみました。
今まではdeviseを使って認証機能を作成していたのですが、Auth0でも「サクッと」できるという噂を聞き、それならばRenderを使ってデプロイまで「サクッと」やってみよう〜ƪ(˘⌣˘)ʃ
そして、案の定つまづきまくったので、備忘録としてまとめました😅

Auth0、Renderとはなんぞや?簡単に説明!!

Auth0とは?

認証・認可プラットフォームの一つです。
Auth0を利用すると、自分で開発したアプリ側では特に特別なコーディングをすることなく、スライドボタンをオンにするだけでfacebookやGoogle、Xなど30種類以上のソーシャルアカウントで簡単サインインができます。複雑なOAuthの実装やトークンの管理などに煩わされることなく、機能の開発に専念することができます。

Renderとは?

Webアプリケーションやバックエンドサービスのデプロイとホスティングを簡素化するクラウドプラットフォームです。
様々なプログラミング言語やフレームワークをサポートしていて、Githubリポジトリを直接連携することで、コードの変更を自動的にデプロイでき、独自のドメインの設定なども可能です。
ダッシュボードから環境変数の管理やログ・メトリクスの確認などもできるため、デプロイに関するプロセスを大幅に簡素化することができます。

開発の手順

Rails側の環境構築をする

Railsアプリケーションを新規作成する

  • rails_auth_appという名前でフォルダーを作成し、Railsをインストールします。
# Homebewを最新化する
$ brew update

# 利用可能なRubyのバージョンを確認する
$ rbenv install -l

# 指定のバージョンのRubyをインストールする
$ rbenv install 3.0.2

# デフォルトのRubyを設定する
$ rbenv global 3.0.2

# Bundlerをインストールする
$ gem install bundler

3 Railsをインストールする
$ gem install rails
  • homeディレクトリを作成してindex.html.erbファイルを作成しておきましょう。
    中身はからで大丈夫です!!
  • ルーティングを記述します
Rails.application.routes.draw do
  root 'home#index'
end

これでいったん、Railsアプリケーション側の作成は終わりです。

Auth0を設定する

Auth0アカウントを作成する

アカウントがない場合は、Auth0のアカウントを作成します。

Auth0アプリケーションにRaislアプリケーションを作成する

  • 左側のメニューバーからApplicationsを選択し、+ Create Applicationのボタンをクリックします。
  • アプリケーションの名前を決めて(わかりやすいようにRailsのアプリ名と同じrails_auth_appとしておきます)Regular Web Applicationsを選択して Crearteボタンをクリックします。
  • 使用するプロジェクトにRuby on Railsを選択します。
    Auth0にRailsのアプリケーションが作成されました。

RailsアプリケーションにAuth0認証を連携する

Quickstartのタブで「I want to integrate with my app」の手順に従い、RailsアプリケーションとAuth0を連携させていきます。

Configure Callback URLs、Configure Logout URLsのURLを設定する

Settingsの
Allowed Callback URLsにhttp://localhost:3000/auth/auth0/callback
Allowed Logout URLsにhttp://localhost:3000
と設定します。

Save Changesをクリックして設定を保存しましょう。

Install the Auth0 SDK

必要なGemをインストールしてアプリケーションに追加していきます。
Gemfileにomniauth-auth0omniauth-rails_csrf_protectionを追加してbundle installしましょう。

Gemfile
gem 'omniauth-auth0', '~> 3.0'
gem 'omniauth-rails_csrf_protection', '~> 1.0' # prevents forged authentication requests

Initialize Auth0 Configuration

Auth0を設定するためのファイルを作成します。

  • config/auth0.ymlを作成する
    Auth0のドメイン、クライアントID、クライアントシークレットなどの情報を設定するファイルで、異なる環境(開発、テスト、本番)ごとに設定を分けることができます。
$ touch config/auth0.yml

コマンドでconfigディレクトリにauth0.ymlという設定ファイルを作成します。
SettingsのBasic informationに記載してある
Domain
Client ID
Client Secret
の値をファイルに記述します。

development:
  auth0_domain: YOUR_DOMAIN
  auth0_client_id: YOUR_CLIENT_ID
  auth0_client_secret: <YOUR AUTH0 CLIENT SECRET>
  • config/initializers/auth0.rbを作成します
$ touch config/initializers/auth0.rb

コマンドでconfig/initializersディレクトリにauth0.rbという設定ファイルを作成します。
このファイルはRailsの初期化プロセス中に読み込まれ、Auth0の設定を初期化し、アプリケーションで使用できるようにします。
config/auth0.ymlからAuth0の設定値を読み込んだり、Auth0のミドルウェアをRailsアプリケーションに組み込みます。
JWTの検証オプションを設定やセッション管理の設定を行なっているのもこちらのファイルです。

AUTH0_CONFIG = Rails.application.config_for(:auth0)

Rails.application.config.middleware.use OmniAuth::Builder do
  provider(
    :auth0,
    AUTH0_CONFIG['auth0_client_id'],
    AUTH0_CONFIG['auth0_client_secret'],
    AUTH0_CONFIG['auth0_domain'],
    callback_path: '/auth/auth0/callback',
    authorize_params: {
      scope: 'openid profile'
    }
  )
end

セキュアモジュールを作成する

deviseでいうauthenticate_user!メソッドのようなログイン必須機能を実装します。
app/controllers/concernsディレクトリにsecured.rbを作成します。

$ touch app/controllers/concerns/secured.rb
module Secured
  extend ActiveSupport::Concern

  included do
    before_action :logged_in_using_omniauth?
  end

  def logged_in_using_omniauth?
    redirect_to '/' unless session[:userinfo].present?
  end
end

コントローラを作成する

Auth0用のコントローラを作成します。

$ rails generate controller auth0 callback failure logout
class Auth0Controller < ApplicationController
  def callback
    auth_info = request.env['omniauth.auth']
    session[:userinfo] = auth_info['extra']['raw_info']

    redirect_to '/dashboard'
  end

  def failure
    @error_msg = request.params['message']
  end

  def logout
    reset_session
    redirect_to logout_url, allow_other_host: true
  end

  private

  AUTH0_CONFIG = Rails.application.config_for(:auth0)

  def logout_url
    request_params = {
      returnTo: root_url,
      client_id: AUTH0_CONFIG['auth0_client_id']
    }

    URI::HTTPS.build(host: AUTH0_CONFIG['auth0_domain'], path: '/v2/logout', query: to_query(request_params)).to_s
  end

  def to_query(hash)
    hash.map { |k, v| "#{k}=#{CGI.escape(v)}" unless v.nil? }.reject(&:nil?).join('&')
  end
end
  • ダッシュボード用のコントローラを作成します
class DashboardController < ApplicationController
  include Secured

  def show
    # session[:userinfo] was saved earlier on Auth0Controller#callback
    @user = session[:userinfo]
  end
end

ルーティングを作成する

Rails.application.routes.draw do
    get 'dashboard', to: 'dashboard#show'

  get '/auth/auth0/callback' => 'auth0#callback'
  get '/auth/failure' => 'auth0#failure'
  get '/auth/logout' => 'auth0#logout'
end

ビューを作成する

  • ログイン・ログアウトボタンを実装します。
<h1>Home index</h1>
<p>Find me in app/views/home/index.html.erb</p>

<%= button_to 'Login', 'auth/auth0', method: :post %>
<%= button_to 'Logout', 'auth/logout', method: :get %>
  • views/dashboard/show.html.erbファイルの中身を作成します
<h1>Dashboard show</h1>
<p>Find me in app/views/dashboard/show.html.erb</p>

<div>
  <p>Normalized User Profile:<%= JSON.pretty_generate(@user[:info])%></p>
  <p>Full User Profile:<%= JSON.pretty_generate(@user[:extra][:raw_info])%></p>
</div>

ローカル環境で動作確認をする

ここまで設定できたら、いよいよrails sです❗️❗️
起動確認したら、ブラウザにlocalhost:3000のURLを入れてHome画面が表示されることを確認します。
Loginボタンをクリックすると、Auth0のログイン画面が表示され、ログイン後はDashboard#showに画面遷移し、ユーザー名が表示されるようになります👏
ここまではなんとか順調順調🎶

デプロイのためのDBを準備する

ローカルの開発環境ではデフォルトのSQLite3というDBを使用していましたが、デプロイするためにはDBを切り替える必要があります。
テストと開発環境はSQlite3のまま、本番環境だけPostgreSQLに変更していきます。

PostgreSQLをインストールする

  • ターミナルにてpostgresqlの最新の安定版をダウンロードします。
$ brew install postgresql
  • データベースを初期化します。
$ initdb /usr/local/var/postgres -E utf8
  • PostgreSQLを立ち上げます
$ brew services start postgresql
  • PostgreSQLを立ち上げた状態でユーザー設定をします。
$ createuser -s -P <ユーザー名>
Enter password for new role: <パスワードを設定>
Enter it again: <再度パスワードを入力>

RailsアプリにPostgreSQLを導入する

  • Gemfileを編集します。
# Use sqlite3 as the database for Active Record
- gem "sqlite3", "~> 1.4"

 group :development, :test do
   # Use sqlite3 as the database for Active Record
   gem "sqlite3"
 end

# 本番環境にPostgreSQLを使用する
 group :production do
   # Use PstgreSQL as the database for Active Record
   gem 'pg'
 end
$ bundle install

環境設定をする

ひとまず、PstgreSQLを使用するための環境を設定していきます。

  • database.ymlを編集して下記のように変更します。
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

test:
  <<: *default
  database: db/test.sqlite

production:
  adapter: postgresql
  encoding: unicode
  pool:  <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  database: <任意のデータベース名>
  username: <任意のユーザー名>
  password: <%= ENV['DB_PASSWORD'] %> # .envで設定
  • .envファイルを作成して環境変数を設定します。
    database.ymlファイルはGitHubにアップされるため、本番環境で使用するデータベースのアクセスパスワードなどは.envというファイルを作成し、そこに記入します。
    ymlファイルの最終行にあるRuby式<%= ENV['DB_PASSWORD'] %>は、.envファイルから値を引っ張ってくる指示をしています。

本番環境用にdotenv-railsというGemをインストールします。

group :production do
  gem 'dotenv-rails'
end
$ bundle install
  • プロジェクトフォルダ直下に.env.productionファイルを作成し、パスワードを記入します。
# 任意のパスワードは最初は記載しなくてOK
DB_PASSWORD = '<任意のパスワード>'
  • .env.productionをGitHubにアップしないようにセッティングする
    プロジェクトフォルダ直下に.gitignoreファイルを作成します。
    ※このファイルに記載されたファイルは、gitにcommitされません。
.env.production

データベースを作成する

$ bundle exec rails db:create

Created database 'db/development.sqlite3'
Created database 'db/test.sqlite'

ターミナルメッセージでは作成されるデータベースがsqlite3となっていますが、現在いる環境が開発環境だからなのでOK👌

Renderの設定をする

GitHubと連携してデプロイするための準備をしていきます。

Renderアカウントを作成する

アカウントがない場合は、Renderのアカウントを作成します。

RenderでWebアプリを作成する

  • Newをクリック後、Web Serviceを選択してアプリケーションを作成します。
  • RenderとGitHubを連携させていきます
    Build and deploy from a Git repositoryを選択してNextをクリックします。
    連携させるリポジトリを選択してConnectをクリックします。
  • フォームに必要な情報を記入しましょう
    Name サイト名
    Root Directory 空欄でもOK
    Environment Ruby
    Region Singapore (Southeast Asia)
    Branch main
    Build Command 初期値
    Start Command 初期値
    Plans Free
    Advanced 空欄でOK

Create Web Serviceボタンをクリックします。
これでRenderにアプリケーションが作成されました。

PostgreSQLを作成する

  • Newをクリック後、PostgreSQLを選択してデータベースを作成します。
  • フォームに必要な情報を記入しましょう。
    Name 任意のデータベースの名前
    Database 任意のデータベースの名前
    User ユーザー名
    Region Singapore (Southeast Asia)
    PostgreSQL Version 初期値
    Datadog API Key 空欄でOK
    Plans Free

Create Databaseをクリックします。
PosgreSQLでデータベースが作成されます。

環境設定ファイルを準備する

  • puma.rbを編集します
# 初期値は2をRenderの公式ドキュメントに記載してある4に変更する
workers ENV.fetch("WEB_CONCURRENCY") { 4 }

preload_app!
  • production.rbを編集します。
- config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?

+ config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? || ENV['RENDER'].present?
  • render-build.shを記載します。
    本番環境で走らせるコマンドをrender-build.shファイルに書きます。
    bun/render-build.shを作成し、Renderの公式ドキュメントに従ってコマンドを記載します。
#!/usr/bin/env bash
# exit on error
set -o errexit

bundle install
bundle exec rake assets:precompile
bundle exec rake assets:clean
bundle exec rake db:migrate
  • render-build.shが実行されるように設定します。
    本番環境が立ち上がるタイミングでrender-build.shが実行されるように、RenderのSettingsのBuild Commandを変更します。
- $ bundle install; bundle exec rake assets:precompile; bundle exec rake assets:clean;
+ $ ./bin/render-build.sh
  • render.yamlファイルを作成して本番環境用の設定を書いていきます。
    プロジェクトフォルダ直下にrender.yamlファイルを作成しましょう。
databases:
  - name: <設定したデータベース名>
    databaseName: <設定したデータベース名>
    user: <設定したユーザー名>
    region: singapore

services:
  - type: web
    name: <設定したデータベース名>
    env: ruby
    region: singapore
    buildCommand: "./bin/render-build.sh"
    startCommand: "bundle exec puma -C config/puma.rb"
    envVars:
      - key: <%= ENV['DATABASE_URL'] %>
        fromDatabase:
          name: render_app
          property: connectionString
      - key: <%= ENV['RAILS_MASTER_KEY'] %>
        sync: false
  • RenderのStartCommand部分を書き換えます。
    render.yamlファイルに記載のあるstartCommand: "bundle exec puma -C config/puma.rb"をRenderのSettings Start Commandを参考に変更しましょう。
- $ bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-development}
+ $ bundle exec puma -C config/puma.rb

環境変数を設定する

Render側の設定

EnvironmentからAdd Environment Variableをクリックして、render.yamlで設定したkeyValueの値を設定します。

  • DATABASE_URL(Key)
    データベースのConnectionsに記載してある値Internal Database URL(Value)
  • RAILS_MASTER_KEY(Key)
    configmaster.keyに記載されている値(Value)

Save Changesボタンをクリックしてせて意を変更しましょう。

Rails側の設定

RenderのConnectionsの値をいれます。

DB_PASSWORD = 'Passwordの値'
DATABASE_URL = 'Internal Database URLの値'
RAILS_MASTER_KEY = 'master.keyの値'

変更したファイルをGitHubにpushしたら、デプロイする準備が始まります!!

デプロイ後の動作確認

アプリケーションのURLはRenderのダッシュボードでアプリのタイトル下に表示されています。
URLをクリックすると開発環境の時と同様の画面が表示されるはずです。

一旦設定ができてしまえば、ファイルを変更してプッシュするたびに本番環境が自動で更新してくれます🎶

認証機能サービスによる特徴

認証機能としてのDeviseはRailsアプリケーションに適した豊富な機能を提供してくれ、導入が非常に簡単であるという利点があります。オープンソースで無料で利用できるため、コストの面でも優れています。ただし、メンテナンスやスケーリングは開発者側で行う必要があります。

一方、Auth0は認証に特化したサービスとして、多くの機能やカスタマイズ性を提供してくれます。また、Auth0側でメンテナンスやスケーリングを行うため、開発者の負担が少なく、高いセキュリティを確保できます。
ただし、大規模なアプリケーションでは有料プランが必要になる場合があります。

比較項目 Devise Auth0
機能の豊富さ ◯ Railsアプリケーションに適した豊富な機能を提供 ◎ 認証に関する多くの機能を提供し、カスタマイズ性も高い
導入の容易さ ◎ Railsアプリケーションへの導入が非常に簡単 ○ 導入はやや複雑だが、ドキュメントが充実している
メンテナンス ○ Gemのアップデートが必要だが、比較的安定している ◎ Auth0側でメンテナンスを行うため、開発者の負担が少ない
カスタマイズ性 ○ Deviseの機能をカスタマイズすることが可能 ◎ Auth0の管理画面で多くの設定やカスタマイズが可能
外部サービス連携 △ OmniAuthを使って外部サービス連携が可能だが、設定が必要 ◎ 多くの外部サービスとの連携が容易に可能
コスト ◎ オープンソースで無料で利用可能 △ 無料プランもあるが、大規模なアプリケーションでは有料プランが必要
スケーラビリティ ○ アプリケーションのスケーリングに合わせて対応可能 ◎ Auth0側でスケーリングを行うため、アプリケーションの規模に関係なく利用可能
セキュリティ ○ 一般的な認証機能のセキュリティは確保されている ◎ 専門的な認証サービスとして高いセキュリティを提供

◎:非常に優れている
○:優れている
△:状況によって異なる

まとめ

今回のように外部サービスを利用することで、比較的簡単に色々な機能を利用することができるため、開発者がアプリケーションの開発に集中することができます。
一方で、仕組みがわからないままアプリケーションに組み込んでしまっては、後のメンテナンスなどで非常に苦労することになりそうです。
アプリケーションの要件や規模、予算などを考慮して、適切なサービスを選択することの重要性を改めて感じました。

今後の展望

もともと、フロントエンドにNext.js ✖️ バックエンドにRailsをAPIとして利用した、Auth0による認証機能を実装したアプリケーションを作成したいと考えており、今回はその前段階のお試し実装でした。
APIの機序なども全くわかっていないため、まだまだ先のお話になりそうですが、ひとまず、Auth0のRails APIバージョンを作成しておおまかに流れを掴めたら良いなと思っています。

参考資料

Discussion