はじめに
こんにちは、つよしと申します。転職のために、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をクリックしてください。
- NameとIdentifierを入力してください。
Nameはアプリ名で大丈夫です
Identifierはhttps://アプリ名-auth-apiにしておきましょう
今回はtestとしました。
signing AlgorithmはRS256のままで大丈夫です。
- Identifierの値を控えておいてください。
入力できたらCreateをクリックします。
- ドメインを控えます。Testタブに移動し、url(domain)を控えてください。
オレンジ枠の部分のみ控えてください。
→ https://[TENANT_NAME].auth0.com/
以上でAuth0の設定は終わりです。
Rails アプリ設定 Auth0
- railsアプリを作成します
rails new api --api
- Gemfileに以下の3つを記述し、bundle installします
gem 'dotenv-rails'
gem 'jwt'
gem 'rack-cors'
bundle install
- .envファイルを作成し、identifierとDomainを使用できるようにします。
※ gitにpushする場合は.gitignoreに.envを記述してください
touch .env
AUTH0_DOMAIN=[domain]
# 例:https://hogehoge.auth0.com/
AUTH0_IDENTIFIER=[identifier]
例:https://test-auth-api
※ []はいりません。文字列のみ入力してください。
- 環境変数が正しく出力できるか確認します
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フォルダを作成します。
mkdir app/lib
- app/lib/にjson_web_token.rbを作成します。
touch app/lib/json_web_token.rb
- 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フォルダを作成します
mkdir app/services
- app/services/にauthorization_service.rbを作成します
touch app/services/authorization_service.rb
- 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を作成します
touch app/controllers/secured_controller.rb
- securedcontroller.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を以下に変更
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:多で構成します
rails g model User sub:string
rails g model Post user:references title:string caption:text
rails db:migrate
- 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を作成します
rails g controller api/v1/posts
- 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を以下の様に記述します
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :posts
end
end
end
いよいよAPI通信確認です!!
- auth0のサイトで、テスト用のtokenをコピーしましょう
オレンジ枠のaccess_tokenの値のみをコピーしてください。
- railsサーバーを立ち上げます
rails s
- 別のターミナルで立ち上げて、以下のGETリクエストを実行します。
curl http://localhost:3000/api/v1/posts
→ []
postsにはまだなにも入っていないので、空のデータが帰ってきます。
- 以下のPOSTリクエストを実行します。
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を挿入してください。
curl -H "Content-Type: application/json" -H "Authorization: bearer [ACCESS_TOKEN]" -d '{"title":"タイトル1", "caption":"説明1"}' -X POST http://localhost:3000/api/v1/posts
以下の様なレスポンスが返ってくれば成功です!!
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リクエストしてみましょう。
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を使用したユーザー認証を機能を実装することが出来ました。
次は
を御覧ください~