LoginSignup
5
5

More than 1 year has passed since last update.

【画像手順解説】Auth0 Rails(api) + React SPAでユーザー認証機能を実装しよう 2 (Rails編)

Last updated at Posted at 2021-07-29

はじめに

こんにちは、つよしと申します。転職のために、railsとreactでユーザー認証付きのSPAのポートフォリオを作成しました。

この記事では、Auth0 Rails(api) + React SPAでユーザー認証を実装する方法を解説します

初学者のため、間違っている情報があるかもしれません。
その場合は、ご指摘,もしくは適宜読み替えて勧めていただけたらと思います。


この記事は3部構成になっています。

【画像手順解説】Auth0 Rails(api) + React SPAでユーザー認証機能を実装しよう 2 (Rails編)
→ 本記事

この記事で最終的に出来ること

  • Rails(API) + React SPAでユーザー認証機能を実装
  • current_user機能
  • Authenticate_user機能
  • 新しいユーザーがサインアップした場合は自動でユーザーをクリエイト

個別での使用方法も学べるので、どちらか一方の実装する方も参考になると思います。

本記事で解説すること

  • Rails(api)でのAuth0設定
  • current_user機能
  • Authenticate_user機能
  • 新しいユーザーがサインアップした場合は自動でユーザーをクリエイト

この記事でしないこと

機能を詳細に説明はせず、手順を丁寧に書いた記事にします。
参考となる記事を随所で貼るので、気になる方はそちらを御覧ください。

目次

  • Auth0とは?
  • どのような機構?
  • Rails Auth0設定
  • Rails アプリ設定

Auth0とは?

どのような機構

Rails Auth0設定

基本的に以下の記事を参考にして制作します

Rails Auth0設定

  • 左のタブでApplication→APIsを選択し、右上のCreate APIをクリックしてください。

全画面_2021_07_29_10_51.png

  • NameとIdentifierを入力してください。

Nameはアプリ名で大丈夫です
Identifierはhttps://アプリ名-auth-apiにしておきましょう
今回はtestとしました。
signing AlgorithmはRS256のままで大丈夫です。

  • Identifierの値を控えておいてください。

全画面_2021_07_29_10_55.png

入力できたらCreateをクリックします。


  • ドメインを控えます。Testタブに移動し、url(domain)を控えてください。

オレンジ枠の部分のみ控えてください。
→ https://[TENANT_NAME].auth0.com/

find-auth0-domain.png

以上でAuth0の設定は終わりです。

Rails アプリ設定 Auth0

  • railsアプリを作成します
console
rails new api --api
  • Gemfileに以下の3つを記述し、bundle installします
gem 'dotenv-rails'
gem 'jwt'
gem 'rack-cors'
console
bundle install
  • .envファイルを作成し、identifierとDomainを使用できるようにします。

※ gitにpushする場合は.gitignoreに.envを記述してください

console
touch .env
AUTH0_DOMAIN=[domain]
# 例:https://hogehoge.auth0.com/

AUTH0_IDENTIFIER=[identifier]
  例:https://test-auth-api

 ※ []はいりません。文字列のみ入力してください。
  • 環境変数が正しく出力できるか確認します
console
irb(main):001:0> ENV['AUTH0_DOMAIN']
=> "https://hogehoge.auth0.com/"
irb(main):002:0> ENV['AUTH0_IDENTIFIER']
=> "https://test-auth-api"

ここから少し複雑になるので

  • ディレクトリ構成の確認
  • コードをコピーしてタイプミス防止

以上の2点に気をつけてください。

各種ファイルについての役割は最後に説明します。


  • app配下にlibフォルダを作成します。
console
mkdir app/lib          
  • app/lib/にjson_web_token.rbを作成します。
console
touch app/lib/json_web_token.rb
  • json_web_token.rbに以下を記述します。
app/lib/json_web_token.rb
# app/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: ENV['AUTH0_DOMAIN'],
              verify_iss: true,
              aud: ENV['AUTH0_IDENTIFIER'],
              verify_aud: true) do |header|
      jwks_hash[header['kid']]
    end
  end

  def self.jwks_hash
    jwks_raw = Net::HTTP.get URI("#{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


  • app配下にservicesフォルダを作成します
console
mkdir app/services
  • app/services/にauthorization_service.rbを作成します
console
touch app/services/authorization_service.rb
  • authorization_service.rbに以下を記述します
app/services/authorization_service.rb
class AuthorizationService
  def initialize(headers = {})
    @headers = headers
  end

  def current_user
    @auth_payload, @auth_header = verify_token
    @user = User.from_token_payload(@auth_payload)
  end

  private

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

  def verify_token
    JsonWebToken.verify(http_token)
  end
end

  • app/controllersにsecured_controllersを作成します
console
touch app/controllers/secured_controller.rb
  • securedcontroller.rbに以下を記述します
app/controllers/secured_controller.rb
class SecuredController < ApplicationController
  before_action :authorize_request

  private

  def authorize_request
    authorize_request = AuthorizationService.new(request.headers)
    @current_user = authorize_request.current_user
  rescue JWT::VerificationError, JWT::DecodeError
    render json: { errors: ['Not Authenticated'] }, status: :unauthorized
  end
end
  • config/initializers/wrap_parameters.rbを以下に変更
config/initializers/wrap_parameters.rb
wrap_parameters format: []

以上でAuth0に必要な設定が終わりました。

これで、以下を実装できています。
- current_user機能
- Authenticate_user機能
- 新規サインアップ時にユーザーを自動でcreate

各種ファイルの役割について軽く説明します。

json_web_token.rb

HTTPリクエストのheaderに添付されたtoken情報を解析し、Auth0の情報と照らし合わせて、人間が読めるユーザー情報に変換します。

authorization_service.rb

■ def verify_token
json_web_token.rbを実行し、tokenを渡しています。

■ def current_user
User.from_token_payload(@auth_payload)で、ユーザーモデルのメソッドを実行し、ユーザーcreateもしくはユーザー情報を返します。
ユーザーモデルのメソッドは後で実装します。

secured_controller.rb

ApplicationControllerを継承し、すべてのコントローラー実行前にauthorize_requestを実行します。

tokenを解析した結果、ユーザー認証出来なかった場合は

errors: ['Not Authenticated'] }, status: :unauthorized

を返します。

これで、Authenticate_user機能(deviseとは逆ですが...)
が実装できました。

Rails アプリ設定 通信確認

それでは実際にモデルを作成して、通信をしてみます。

ユーザーモデルとポストモデルを作成します。
ユーザーとポストは 1:多で構成します

console
rails g model User sub:string
rails g model Post user:references title:string caption:text

rails db:migrate
  • user.rbに以下を記述してください。
user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :destroy

  def self.from_token_payload(payload)
    find_by(sub: payload['sub']) || create!(sub: payload['sub'])
  end
end

■ def self.from_token_payload(payload)
token情報を参照し、userが存在する場合はuserを返す、存在しない場合はuserをcreateします。


  • posts_controllerを作成します
console
rails g controller api/v1/posts
  • posts_controller.rbを以下の様に編集します
posts_controller.rb
class Api::V1::PostsController < SecuredController #SecuredContollerを継承する
  skip_before_action :authorize_request, only: [:index,:show]

  def index
    posts = Post.all
    render json: posts
  end

  def show
    post = Post.find(params[:id])
    render json: post
  end

  def create
    post = @current_user.posts.build(post_params)

    if post.save
      render json: post
    else
      render json: post.errors, status: :unprocessable_entity
    end
  end

  def destroy
    post = Post.find(params[:id])
    post.delete
  end

  private

  def post_params
    params.permit(:title,:caption)
  end
end

■ skip_before_action
ユーザー認証をスキップするアクションを記入します。
今回はindex,showアクションのみユーザー認証をスキップします。


  • routes.rbを以下の様に記述します
routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :posts
    end
  end
end

いよいよAPI通信確認です!!

  • auth0のサイトで、テスト用のtokenをコピーしましょう

オレンジ枠のaccess_tokenの値のみをコピーしてください。

find-access-token.png

  • railsサーバーを立ち上げます
console
rails s
  • 別のターミナルで立ち上げて、以下のGETリクエストを実行します。
console
curl http://localhost:3000/api/v1/posts

→ []

postsにはまだなにも入っていないので、空のデータが帰ってきます。

  • 以下のPOSTリクエストを実行します。
console
curl -H "Content-Type: application/json" -d '{"title":"タイトル1", "caption":"説明1"}' -X POST http://localhost:3000/api/v1/posts

→ {"errors":["Not Authenticated"]}

headerにtokenを載せていないので、エラーが帰ってきます!!
ちゃんとユーザー認証出来ていますね。

それでは最後にtokenを載せてPOSTリクエストを実行します。
[ACCESS_TOKEN]のところにコピーしたtokenを挿入してください。

console
curl -H "Content-Type: application/json" -H "Authorization: bearer [ACCESS_TOKEN]" -d '{"title":"タイトル1", "caption":"説明1"}' -X POST http://localhost:3000/api/v1/posts

以下の様なレスポンスが返ってくれば成功です!!

rails_log
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."sub" = ? LIMIT ?  [["sub", "ZGLom4PI88yF3FvKd4tctFgKldVNDxHZ@clients"], ["LIMIT", 1]]
  ↳ app/models/user.rb:4:in `from_token_payload'
   (0.1ms)  begin transaction
  ↳ app/models/user.rb:4:in `from_token_payload'

  User Create (0.6ms)  INSERT INTO "users" ("sub", "created_at", "updated_at") VALUES (?, ?, ?)  [["sub", "ZGLom4PI88yF3FvKd4tctFgKldVNDxHZ@clients"], ["created_at", "2021-07-29 08:50:47.913221"], ["updated_at", "2021-07-29 08:50:47.913221"]]
  ↳ app/models/user.rb:4:in `from_token_payload'
   (1.5ms)  commit transaction
# UserがCreateされている

  ↳ app/models/user.rb:4:in `from_token_payload'
   (0.1ms)  begin transaction
  ↳ app/controllers/api/v1/posts_controller.rb:17:in `create'
  Post Create (0.5ms)  INSERT INTO "posts" ("user_id", "title", "caption", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["user_id", 3], ["title", "タイトル1"], ["caption", "説明1"], ["created_at", "2021-07-29 08:50:47.930796"], ["updated_at", "2021-07-29 08:50:47.930796"]]
  ↳ app/controllers/api/v1/posts_controller.rb:17:in `create'
   (1.0ms)  commit transaction
  ↳ app/controllers/api/v1/posts_controller.rb:17:in `create'
Completed 200 OK in 563ms (Views: 0.4ms | ActiveRecord: 6.5ms | Allocations: 13691
  • 最初にUserがCreateされている
  • PostがCreateされている

以上2点を確認してください!!

もう一度GETリクエストしてみましょう。

console
curl http://localhost:3000/api/v1/posts

→ [{"id":1,"user_id":1,"title":"タイトル1","caption":"説明1","created_at":"2021-07-29T08:57:09.782Z","updated_at":"2021-07-29T08:57:09.782Z"}]

以上の様に返ってくれば成功です!!


お疲れ様でした!!

Rails(api)でのAuth0を使用したユーザー認証を機能を実装することが出来ました。

次は

を御覧ください~

5
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5