Open12
rails6とreactでdeviseを使ったログイン認証
詰まったところが多くあったのでメモ
簡単な構成
- Rails6 API
- React
- devise, devise-jwt
- omniauth 2.0
参考資料
CookieOverflowの発生
ActionDispatch::Cookies::CookieOverflow in Api::V1::HogeController#index
undefined method `dta_find_by' for User:Class
-
include DeviseTokenAuth::Concerns::User
が modelに存在しないため発生するエラー
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
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
動作確認
- 事前準備 (ユーザの作成)
bin/rails r 'User.create(email: "test@example.com", password: "password")'
- 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
参考
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
参考
コンテンツの呼び出し
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の対応
- サインインに成功したときに発行されるtokenを保存
const res = await signIn(params)
if (res.status === 200) {
// ログインに成功した場合はCookieに各値を格納
Cookies.set("_authorization", res.headers["authorization"]);
}
- コンテンツを呼び出すときに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
);
常に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
参考
その他エラー
Api::V1::Auth::SessionsController:0x0000000000a0f0
undefined local variable or method `flash' for #今回はAPIモードであるため、Flashを使用したくない
devise.rbで下記設定を行うことで解決
config.navigational_formats = []
No verification key available
dockerを使ってcredentials.yml.enc
を修正するときは下記を参考
下記の順序でいけるそう
- credentials.yml.encとmaster.keyを削除
- 上記HPのようにdocker-compose.ymlを編集
- docker-compose run web rails credentials:edit
Signature verification raised
メールアドレスからログイン
confirmations_controller 使用?
メール対応
apikeyの設定
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に変更する必要がある
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
認証メールの確認
apiモードに合わせてレスポンスをカスタマイズする
パスワード対応