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

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

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

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

---

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

https://qiita.com/pon0204/items/ea6b68043ca11c2b5156

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

https://qiita.com/pon0204/items/50838829bd8932b37820

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

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

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

## 本記事で解説すること
- Rails(api)でのAuth0設定
- current_user機能
- Authenticate_user機能
- 新しいユーザーがサインアップした場合は自動でユーザーをクリエイト

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

## 目次
- Auth0とは?
- どのような機構?
- Rails Auth0設定
- Rails アプリ設定

## Auth0とは?
https://qiita.com/furuth/items/68c3caa3127cbf4f6b77

## どのような機構
https://dev.classmethod.jp/articles/auth0-architecture/

## Rails Auth0設定

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

https://auth0.com/blog/building-secure-apis-with-rails-6-and-auth0/

## Rails Auth0設定
- 左のタブでApplication→APIsを選択し、右上のCreate APIをクリックしてください。

![全画面_2021_07_29_10_51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/900110/8b4f77c1-e0ed-9713-47f4-446ba5a0ef7b.png)

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

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

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


![全画面_2021_07_29_10_55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/900110/4c910ce5-efa1-4188-9084-274c07285e72.png)

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

---

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

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

![find-auth0-domain.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/900110/447a42d8-85e7-4ec9-1e8c-e1ddeb798986.png)

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

## Rails アプリ設定 Auth0

- railsアプリを作成します

```console:console
rails new api --api
```

- Gemfileに以下の3つを記述し、bundle installします

```Gemfile
gem 'dotenv-rails'
gem 'jwt'
gem 'rack-cors'
```

```console:console
bundle install
```

- .envファイルを作成し、identifierとDomainを使用できるようにします。

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

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

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

 ※ []はいりません。文字列のみ入力してください。
```

- 環境変数が正しく出力できるか確認します

```console: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:console
mkdir app/lib          
```

- app/lib/にjson_web_token.rbを作成します。

```console: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:console
mkdir app/services
```

- app/services/にauthorization_service.rbを作成します

```console: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: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: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: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/900110/0ae2ee6f-c84f-dbe3-fe99-8ae0e8f954da.png)

- railsサーバーを立ち上げます

```console:console
rails s
```

- 別のターミナルで立ち上げて、以下のGETリクエストを実行します。

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

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

- 以下のPOSTリクエストを実行します。

```console: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: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

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

```console: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: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を使用したユーザー認証を機能を実装することが出来ました。

次は

https://qiita.com/pon0204/items/50838829bd8932b37820

を御覧ください~