Help us understand the problem. What is going on with this article?

Rails5にAuth0を利用して認証機構を最速で追加して、爆速開発環境を構築していくぜ!

More than 1 year has passed since last update.

Gakuです。
Rails楽しいっすね。やっぱRailsだわ!と思っている今日このごろ。
ただ、どんなフレームワークを使ったとしても「認証」部分の実装でいつも消耗しちゃいますよね。
なので、今回はAuth0を使って、「サクサク」っと認証を追加する方法を備忘録程度に記載していきたいと思います。

以下のauth0の公式ドキュメントを参考に実装していきます。
https://auth0.com/docs/quickstart/backend/rails/01-authorization

また、railsの設定等は事前に済ませている前提です。
設定等のやり方がわからない方は、以前書いたこちらの記事等を参考にしてもらえると幸いです。
数年ぶりのRails入門【セットアップ編】

今回作成したコード類は以下にまとめています。よければご参照ください。
gaku3601/rails-auth0

Auth0に登録、APIを作成

まだ、Auth0に登録をしていない人は、ひとまず以下から登録しておきましょう。
Auth0
登録が完了したら、新しくTenant(Auth0はtenant単位で管理します。)を作成します。
[Tenant名].auth0.comはドメインで後ほど設定しますので、メモっておきましょう。
スクリーンショット 2019-04-08 15.44.49.png
また、サイドメニューのAPIsからAPIを作成します。
スクリーンショット 2019-04-08 15.33.24.png
今回はnameはbackendで、Identifierは「https://backend」とし、作成しました。
Identifierで設定した値は後ほど使いますので、これもメモっておきましょう。

実装

railsに認証機能を実装していきます。
必要なGemであるjwtとdotenv-railsをGemfileに追記し、bundle installしておきます。

Gemfile
gem 'jwt' #追記
group :development do
  gem 'dotenv-rails' # 追記
end

lib配下にjson_web_token.rbを作成し、以下の内容で作成します。

lib/json_web_token.rb
require 'net/http'
require 'uri'

class JsonWebToken
  def self.verify(token)
    JWT.decode(token, nil,
               true, # Verify the signature of this token
               algorithm: 'RS256',
               iss: "https://#{ENV['AUTH0_DOMAIN']}/",
               verify_iss: true,
               aud: ENV['AUTH0_AUDIENCE'],
               verify_aud: true) do |header|
      jwks_hash[header['kid']]
    end
  end

  def self.jwks_hash
    jwks_raw = Net::HTTP.get URI("https://#{ENV['AUTH0_DOMAIN']}/.well-known/jwks.json")
    jwks_keys = Array(JSON.parse(jwks_raw)['keys'])
    Hash[
      jwks_keys
      .map do |k|
        [
          k['kid'],
          OpenSSL::X509::Certificate.new(
            Base64.decode64(k['x5c'].first)
          ).public_key
        ]
      end
    ]
  end
end

作成したjson_web_token.rbはinitializersとして登録しておくため、config/initializers/jwt.rbを作成します。

config/initializers/jwt.rb
require 'json_web_token'

認証部分の実装をしていきます。
user modelを作成し以下のように編集。

コマンド
rails g model user sub:string
app/models/user.rb
class User < ApplicationRecord
    def self.from_token_payload(payload)
        find_by(sub: payload['sub']) || create!(sub: payload['sub'])
    end
end

app/controllers/concerns/secured.rbを作成し、以下のように記載します。

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

    SCOPES = {
      '/api/private-scoped' => ['read:messages'] # scopeが必要なrequestはこのように記載
    }

    included do
      before_action :authenticate_request!
    end

    def current_user
      @user
    end

    private

    def authenticate_request!
      @auth_payload, @auth_header = auth_token
      @user = User.from_token_payload(@auth_payload)

      render json: { errors: ['Insufficient scope'] }, status: :forbidden unless scope_included
    rescue JWT::VerificationError, JWT::DecodeError
      render json: { errors: ['Not Authenticated'] }, status: :unauthorized
    end

    def http_token
      if request.headers['Authorization'].present?
        request.headers['Authorization'].split(' ').last
      end
    end

    def auth_token
      JsonWebToken.verify(http_token)
    end

    def scope_included
      # The intersection of the scopes included in the given JWT and the ones in the SCOPES hash needed to access
      # the PATH_INFO, should contain at least one element
      if SCOPES[request.env['PATH_INFO']] == nil
        true
      else
        (String(@auth_payload['scope']).split(' ') & (SCOPES[request.env['PATH_INFO']])).any?
      end
    end
end

ここまでで、認証機能の実装は完了です。
最後にちゃんと動くか確認するため、以下ファイルを作成します。

.env
AUTH0_DOMAIN=[Tenant名].auth0.com # さきほどメモったdomainを記述
AUTH0_AUDIENCE=https://backend # さきほどメモったIdentifierの値を記述
app/controllers/private_controller.rb
class PrivateController < ActionController::API
    include Secured
    def private
      render json: { message: 'Hello from a private endpoint! You need to be authenticated to see this.', user: current_user.id}
    end

    def private_scoped
      render json: { message: 'Hello from a private endpoint! You need to be authenticated and have a scope of read:messages to see this.' }
    end
end
config/routes.rb
Rails.application.routes.draw do
  get 'api/private' => 'private#private'
  get 'api/private-scoped' => 'private#private_scoped'
end

railsを起動して、api/praivate、api/private-scopedにブラウザ等でアクセスしてみましょう。
認証がないので、認証エラーが出ていれば成功です。

access token付きrequestの叩き方

認証はheaderに「Authorization: Bearer accesstoken」を仕込んでリクエストを出し、tokenが正しいものであれば正常値がサーバから返ってきます。
curlコマンドでリクエストを叩く方法もありますが、かなり面倒なので、「postman」を設定し動作確認する方法をオススメします。
ここでは多くを語りませんが、認証設定の例を画像として掲載しておきます。(書き方が独特なので、ハマりやすいので。。。)
スクリーンショット 2019-04-08 16.39.44.png
APIsを作成した際、Applicationsの方にも同じ名前でアプリケーションが作成されているので、その設定値を元に設定します。
TypeをOauth2で設定し、こんな感じで記述してあげることで、access token付きのrequestを簡単に叩けるようになるのでオススメです。

おわりに

コピペで認証付きのAPIを作成することができました。
railsでお馴染みのdeviseを使った認証機能の実装よりかは、コードも読みやすいし、精神衛生上悪くないな〜と感じています。
Auth0は無料枠もかなりあるので、基本無料でokですし興味がある方は一度触ってみるのはありなサービスかと思ってます。

それでは(´・ω・`)ノシ

gaku3601
🎉筋肉系エンジニアになりたい系プログラマ٩( 'ω' )و🎉 好きなことで食っていく。(`・ω・´) twitterとかもやってますので、絡んでいただけると嬉しく思います♪
persol-pt
労働人口の減少が見込まれる現代において、 一人ひとりが生み出す付加価値を最大化することが、 日本における重要なテーマです。 わたしたちは、 人・プロセスデザイン・テクノロジーの力で、 お客さまのビジネスプロセスに変革をもたらし、 人と組織の生産性を高めていきます。 さらに、はたらくを楽しむことを大切にし、 「はたらいて、笑おう。」の世界を実現します。
https://www.persol-pt.co.jp/
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