Open12

rails6とreactでdeviseを使ったログイン認証

yjiro0403yjiro0403
undefined method `dta_find_by' for User:Class
  • include DeviseTokenAuth::Concerns::Userが modelに存在しないため発生するエラー
yjiro0403yjiro0403

cors設定

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins ENV['CORS_ORIGINS_URL']

    resource '*',
      headers: :any,
      expose: ["access-token", "expiry", "token-type", "uid", "client"], # 認証のために追記
      methods: [:get, :post, :put, :patch, :delete, :options, :head],
      expose: %w(Authorization),
      credentials: true
  end
end
yjiro0403yjiro0403

routeの設定

devise_forの位置でレスポンスの内容が違う

Rails.application.routes.draw do
  devise_for :users

  namespace :api do
    namespace :v1 do
      devise_for :users
    end
  end
end
curl -D - -o /dev/null http://localhost:3001/api/v1/users/sign_in -H 'content-type: application/json' -d '{"user": {"email": "test@example.com", "password": "password"}}' 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0HTTP/1.1 404 Not Found
Content-Type: application/json; charset=UTF-8
X-Request-Id: cea9bfd8-650f-4c78-8724-db18398b53de
X-Runtime: 0.036508
Vary: Origin
Content-Length: 13629
  • 404になる

よって、今回は一番上にdevise_forを置きpathを設定する方法で実装

Rails.application.routes.draw do
  devise_for :users, path: 'api/v1/users'
end
yjiro0403yjiro0403

動作確認

  1. 事前準備 (ユーザの作成)
bin/rails r 'User.create(email: "test@example.com", password: "password")'
  1. API呼び出し
curl -D - -o /dev/null http://localhost:3001/api/v1/users/sign_in -H 'content-type: application/json' -d '{"user": {"email": "test@example.com", "password": "password"}}'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0HTTP/1.1 302 Found
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Location: http://localhost:3001/
Content-Type: text/html; charset=utf-8
Authorization: "jwtトークンの内容"
Set-Cookie: "cookieの内容"
Cache-Control: no-cache
X-Request-Id: 0bb0bb71-f4b4-4bfd-9579-edc7b4a58506
X-Runtime: 0.286296
Vary: Origin
Transfer-Encoding: chunked

参考

https://www.nightswinger.dev/2020/02/jwt-authentication-for-rails/#動作確認

yjiro0403yjiro0403

controllerの作成

routes

devise_for :users, path: 'api/v1/users', controllers: {
    sessions: 'api/v1/auth/sessions'
  }

controllerの作成

class Api::V1::Auth::SessionsController < Devise::SessionsController
  respond_to :json

    private
  
    def respond_with(resource, _opts = {})
      render json: { message: 'You are logged in.' }, status: :ok
    end
  
    def respond_to_on_destroy
      log_out_success && return if current_user
  
      log_out_failure
    end
  
    def log_out_success
      render json: { message: "You are logged out." }, status: :ok
    end
  
    def log_out_failure
      render json: { message: "Hmm nothing happened."}, status: :unauthorized
    end
end

参考

https://ichi.pro/ruby-onrails-de-yu-za-o-ninshosuru-tame-no-devise-jwt-chu-toriaru-222244300665230

yjiro0403yjiro0403

コンテンツの呼び出し

class Api::V1::ContentsController < ApplicationController
  before_action :authenticate_user!

  def index
    ~~
  end

end

Tokenを渡していない場合、下記のレスポンスが返ってくる

{
  "errors": [
    "You need to sign in or sign up before continuing."
  ]
}

reactの対応

  1. サインインに成功したときに発行されるtokenを保存
      const res = await signIn(params)

      if (res.status === 200) {
        // ログインに成功した場合はCookieに各値を格納
        Cookies.set("_authorization", res.headers["authorization"]);
}
  1. コンテンツを呼び出すときにtokenを付与
const res = await client.get<ContentType[]>('/contents', {
      headers: {
        Authorization: Cookies.get("_authorization") || ""
      }
    });

applyCaseMiddlewareを使用するときは下記のようにする

const res = await authorizedClient.get<ContentType[]>('/private/contents');
const authorizedClient = applyCaseMiddleware(
  axios.create({
    baseURL: 'http://localhost:3001/api/v1',
    headers: {
      Authorization: Cookies.get("_authorization") || ""
    }
  }),
  options
);
yjiro0403yjiro0403

常にtokenを返すように設定

  • 認証付きAPIを呼び出した時に、レスポンスの内容に加え最新のtokenを返す

config/initializers/devise.rbで下記を追記

config.jwt do |jwt|
    jwt.secret = Rails.application.credentials[:jwt_secret_key]
   jwt.dispatch_requests = [
      ['POST', %r{^/login$}]
    ]
    jwt.revocation_requests = [
      ['DELETE', %r{^/logout$}]
    ]
    jwt.expiration_time = 30.minutes.to_i
end

参考

https://github.com/DakotaLMartinez/rails-devise-jwt-tutorial

yjiro0403yjiro0403

その他エラー

undefined local variable or method `flash' for #Api::V1::Auth::SessionsController:0x0000000000a0f0

今回はAPIモードであるため、Flashを使用したくない
devise.rbで下記設定を行うことで解決

  config.navigational_formats = []

No verification key available

https://hirocorpblog.com/rails-credentials-master/

dockerを使ってcredentials.yml.encを修正するときは下記を参考
https://qiita.com/at-946/items/8630ddd411d1e6a651c6
下記の順序でいけるそう

  1. credentials.yml.encとmaster.keyを削除
  2. 上記HPのようにdocker-compose.ymlを編集
  3. docker-compose run web rails credentials:edit

Signature verification raised

yjiro0403yjiro0403

メール対応

https://asalworld.com/rails-heroku-sendgrid/

https://williamafil.github.io/notes/2021/06/28/Devise-Confirmable-with-SendGrid/

https://k-koh.hatenablog.com/entry/2020/09/22/164141

apikeyの設定

https://blog.kozakana.net/2018/05/rails-sendgrid-smtp/

Senderの設定

下記エラーが出たので対応

Net::SMTPFatalError (550 The from address does not match a verified Sender Identity. Mail cannot be sent until this error is resolved. Visit https://sendgrid.com/docs/for-developers/sending-email/sender-identity/ to see the Sender Identity requirements
From: please-change-me-at-config-initializers-devise@example.com
web_1  | Reply-To: please-change-me-at-config-initializers-devise@example.com
web_1  | To: 送信先
web_1  | Message-ID: <621f2d5372cc4_150c8-484@51b14ea72fd5.mail>
web_1  | Subject: Confirmation instructions
web_1  | Mime-Version: 1.0
web_1  | Content-Type: text/html;
web_1  |  charset=UTF-8
web_1  | Content-Transfer-Encoding: 7bit
web_1  | 
web_1  | <p>Welcome 送信先!</p>
web_1  | 
web_1  | <p>You can confirm your account email through the link below:</p>
web_1  | 
web_1  | <p><a href="">Confirm my account</a></p>
web_1  | 
web_1  | Completed 500 Internal Server Error in 1956ms (ActiveRecord: 39.1ms | Allocations: 21968)

コンソールのメール内容を確認するにFromの内容をsendgridで設定したsenderに変更する必要がある

https://qiita.com/ryo_kh/items/9af6cee58223845c66b3

Devise.setup do |config|
  // 送信先を変更
  config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
end

Userモデルにconfirmableを追加

// jwt_revocation_strategyの後ろにおくとエラー
  devise :database_authenticatable, :confirmable,
    :jwt_authenticatable, jwt_revocation_strategy: self

devise独自の認証をスキップ

  • 今回は手動でメールを送信するため
def create
    @user = User.new(
      email: sign_up_params[:email]
    )
    @user.skip_confirmation_notification!
    @user.save
  end

https://qiita.com/ozackiee/items/21fcad4a1564136b9510

認証メールの確認

apiモードに合わせてレスポンスをカスタマイズする

https://qiita.com/mnishiguchi/items/85df424577326f4207b1
https://qiita.com/kogache/items/03714100d34af07b1968

パスワード対応

https://tech.mof-mof.co.jp/blog/devise-override-confirmations-after-select-password/