Devise導入済みのアプリにDevise Token Authを入れて共存させる の続きで、リクエストしながら挙動の確認と設定、不味そうな所のカスタマイズも行いました。結構、大変でした。
登録とメール認証の流れ
アカウント登録(処理)
POST /users/auth(.:format) users/auth/registrations#create
※nameは独自に追加しています。
パラメータなし % curl http://localhost:3000/users/auth -X POST {"success":false,"errors":["リクエストボディに適切なアカウント新規登録データを送信してください。"],"status":"error"} パラメータあり % curl http://localhost:3000/users/auth -X POST -H "Content-Type: application/json" -d '{ "email":"test", "password":"abc12345"}' {"success":false,"errors":["'confirm_success_url' パラメータが与えられていません。"],"status":"error","data":{"id":null,"code":"279a3392c0834c509f383ea4ca3ccb3d","image":{"url":null,"mini":{"url":null},"small":{"url":null},"medium":{"url":null},"large":{"url":null},"xlarge":{"url":null}},"name":null,"email":"test","destroy_requested_at":null,"destroy_schedule_at":null,"created_at":null,"updated_at":null,"provider":"email","uid":"","allow_password_change":false,"nickname":null}} 確認成功URLを追加 % curl http://localhost:3000/users/auth -X POST -H "Content-Type: application/json" -d '{ "email":"test", "password":"abc12345", "confirm_success_url":"http://localhost:3000/sign_in"}' {"status":"error","data":{"id":null,"code":"8ed4797b733586e12191954e5416cb43","image":{"url":null,"mini":{"url":null},"small":{"url":null},"medium":{"url":null},"large":{"url":null},"xlarge":{"url":null}},"name":null,"email":"test","destroy_requested_at":null,"destroy_schedule_at":null,"created_at":null,"updated_at":null,"provider":"email","uid":"","allow_password_change":false,"nickname":null}, "errors":{"name":["入力してください。"],"email":["は有効ではありません"],"full_messages":["氏名 入力してください。","メールアドレス は有効ではありません"]}} 正常なパラメータ % curl http://localhost:3000/users/auth -X POST -H "Content-Type: application/json" -d '{ "name":"test", "email":"test@mydomain.com", "password":"abc12345", "confirm_success_url":"http://localhost:3000/sign_in"}' {"status":"success","data":{"id":30,"code":"31e210e8ede22e1a7053107db1dcd5ea","image":{"url":null,"mini":{"url":null},"small":{"url":null},"medium":{"url":null},"large":{"url":null},"xlarge":{"url":null}},"name":"test","email":"test@mydomain.com","destroy_requested_at":null,"destroy_schedule_at":null,"created_at":"2021-08-08T14:52:16.934+09:00","updated_at":"2021-08-08T14:52:16.934+09:00","provider":"email","uid":"test@mydomain.com","allow_password_change":false,"nickname":null}}
メール送信(メールアドレス確認)もOK。但し、URLのパスが既存のまま。後ほど対応
現状: http://localhost:3000/users/confirmation?confirmation_token=L5xne11CXPYvZmH5xxmf 期待: http://localhost:3000/users/auth/confirmation?confirmation_token=L5xne11CXPYvZmH5xxmf
メールアドレス確認[メール再送](処理)
POST /users/auth/confirmation(.:format) users/auth/confirmations#create
パラメータなし % curl http://localhost:3000/users/auth/confirmation -X POST {"success":false,"errors":["translation missing: ja.devise_token_auth.confirmations.missing_email"]} {"success":false,"errors":["メールアドレスが与えられていません。"]} パラメータあり % curl http://localhost:3000/users/auth/confirmation -X POST -H "Content-Type: application/json" -d '{ "email":"test"}' {"success":false,"errors":["translation missing: ja.devise_token_auth.confirmations.user_not_found"]} {"success":false,"errors":["ユーザーが見つかりません。"]} 正常なパラメータ % curl http://localhost:3000/users/auth/confirmation -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com"}' {"success":true,"message":"translation missing: ja.devise_token_auth.confirmations.sended"} {"success":true,"message":"'test@mydomain.com' にメールアドレス確認の案内が送信されました。"}
メール送信もOK。但し、URLのパスが既存のまま(アカウント登録と同じ)
認証済み(メールのURLをクリック後)に再度、curl投げるとまた送信されてしまう。後日対応
メッセージを日本語化したい
config/locales/devise_token_auth.ja.yml を作成
# Tips: 不足分や文言変更したい場合のみ記載。デフォルト: gems/devise_token_auth-*/config/locales/ja.yml ja: devise_token_auth: confirmations: missing_email: 'メールアドレスが与えられていません。' sended_paranoid: 'ユーザーが見つかりません。' # Tips: user_not_foundで、config.paranoid = trueの場合 user_not_found: 'ユーザーが見つかりません。' sended: "'%{email}' にメールアドレス確認の案内が送信されました。"
メールのメールアドレス確認URLを変更したい
共存させている為、confirmation_urlが変わっている。
また、パラメータも増えている為、修正が必要。
app/views/users/mailer/confirmation_instructions.html.erb
+ <% if message['redirect-url'].to_s.present? %> + <%= link_to 'メールアドレス確認', users_user_confirmation_url(confirmation_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url'].to_s) %><br/> + <% else %> <%= link_to 'メールアドレス確認', confirmation_url(@resource, confirmation_token: @token) %><br/> + <% end %>
app/views/users/mailer/confirmation_instructions.html.erb
+ <% if message['redirect-url'].to_s.present? %> + <%= users_user_confirmation_url(confirmation_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url'].to_s) %> + <% else %> <%= confirmation_url(@resource, confirmation_token: @token) %> + <% end %>
ソースのメールテンプレにも.to_sが付いている。
message[‘redirect-url’].present?が常にtrueになった。
誤って、@resource渡したら、
users_user_confirmation_url(@resource, confirmation_token: @token・・・)
「.数字」がURLに入った。
http://localhost:3000/users/auth/confirmation.53?config=
動作確認
アカウント登録と同じようにconfirm_success_urlを追加してみる % curl http://localhost:3000/users/auth/confirmation -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com", "confirm_success_url":"http://localhost:3000/sign_in"}' {"success":true,"message":"'test@mydomain.com' にメールアドレス確認の案内が送信されました。"} -> NG。redirect_urlが入らない。http://localhost:3000/users/confirmation?confirmation_token=L5xne11CXPYvZmH5xxmf redirect_urlに変更してみる % curl http://localhost:3000/users/auth/confirmation -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com", "redirect_url":"http://localhost:3000/sign_in"}' {"success":true,"message":"'test@mydomain.com' にメールアドレス確認の案内が送信されました。"} -> OK。redirect_urlが入る。http://localhost:3000/users/auth/confirmation?config=default&confirmation_token=L5xne11CXPYvZmH5xxmf&redirect_url=http://localhost:3000/sign_in
メールアドレス確認(処理)
GET /users/auth/confirmation(.:format) users/auth/confirmations#show
メールのURLからアクセスするので、ブラウザで
Tokenなしと不正 http://localhost:3000/users/auth/confirmation http://localhost:3000/users/auth/confirmation?confirmation_token=not -> 404: Routing Error 未認証のToken http://localhost:3000/users/auth/confirmation?confirmation_token=L5xne11CXPYvZmH5xxmf -> ArgumentError in Users::Auth::ConfirmationsController#show -> bad argument (expected URI object or URI string) リダイレクトURL追加 http://localhost:3000/users/auth/confirmation?confirmation_token=L5xne11CXPYvZmH5xxmf&redirect_url=http://localhost:3000/sign_in -> Redirected to http://localhost:3000/sign_in?account_confirmation_success=true
アカウント登録で指定したconfirm_success_urlを、メールのURLのredirect_urlパラメータにセットすれば、成功時に?account_confirmation_success=trueを付けてリダイレクトしてくれる。
もう一度、同じURLにアクセスすると、404: Routing Errorになってしまう。
失敗時に404は解りにくいので、エラーコードを付けてリダイレクトに変更したい。後日対応
デフォルトURLを設定する?
default_confirm_success_urlを設定すると、アカウント登録のconfirm_success_urlが任意になり、redirect_urlが無くてもエラーにならなくなる。
また、メールアドレス確認[メール再送]のredirect_urlを指定しなくても、認証URLのパラメータにURLが追加される。
ただ、バックエンドがフロントを意識する事になり、自由に変えられなくなるので、無くて良いかも。設定するなら、こんな感じ。
config/initializers/devise_token_auth.rb
config.default_confirm_success_url = 'http://localhost:5000/sign_in'
ホワイトリストを設定したい
CORSでリクエスト元を制限する事もできるけど、ブラウザ依存だし、
redirect_urlはURLに埋め込みなので、変更される可能性がないとは言い切れない。
config/initializers/devise_token_auth.rb
config.redirect_whitelist = Settings['redirect_whitelist']
config/settings/development.yml に追加
redirect_whitelist: ['http://localhost:5000/*']
アカウント登録(処理)の動作確認
% curl http://localhost:3000/users/auth -X POST -H "Content-Type: application/json" -d '{ "name":"test2", "email":"test2@mydomain.com", "password":"abc12345", "confirm_success_url":"http://badsite.com/"}' {"success":false,"errors":["'http://badsite.com/' へのリダイレクトは許可されていません。"],"status":"error","data":{"id":null,"code":"e10e29bacffc3d78ca37bf3077d7a321","image":{"url":null,"mini":{"url":null},"small":{"url":null},"medium":{"url":null},"large":{"url":null},"xlarge":{"url":null}},"name":"test","email":"test@mydomain.com","destroy_requested_at":null,"destroy_schedule_at":null,"created_at":null,"updated_at":null,"provider":"email","uid":"","allow_password_change":false,"nickname":null}} % curl http://localhost:3000/users/auth -X POST -H "Content-Type: application/json" -d '{ "name":"test2", "email":"test2@mydomain.com", "password":"abc12345", "confirm_success_url":"http://localhost:5000/sign_in"}' {"status":"success","data":{"id":5,"code":"08133b8f817c6f6effb024de5b4cf378","image":{"url":null,"mini":{"url":null},"small":{"url":null},"medium":{"url":null},"large":{"url":null},"xlarge":{"url":null}},"name":"test2","email":"test2@mydomain.com","destroy_requested_at":null,"destroy_schedule_at":null,"created_at":"2021-08-12T09:55:37.459+09:00","updated_at":"2021-08-12T09:55:37.459+09:00","provider":"email","uid":"test2@mydomain.com","allow_password_change":false,"nickname":null}}%
OK。ホワイトリストが効いている。
メールアドレス確認[メール再送](処理)の動作確認
% curl http://localhost:3000/users/auth/confirmation -X POST -H "Content-Type: application/json" -d '{ "email":"test2@mydomain.com", "redirect_url":"http://badsite.com/"}' {"success":true,"message":"'test2@mydomain.com' にメールアドレス確認の案内が送信されました。"}
NG。送信されてしまう。後日対応
メールアドレス確認(処理)の動作確認
http://localhost:3000/users/auth/confirmation?config=default&confirmation_token=oLARmDJsmrn6srZYzoyK&redirect_url=http://badsite.com/ -> Redirected to http://badsite.com/?account_confirmation_success=true
NG。認証されてしまう。ダメじゃん!後日対応
認証(ログインとログアウト)
ログイン(処理)
users_user_session POST /users/auth/sign_in(.:format) users/auth/sessions#create
パラメータなし % curl http://localhost:3000/users/auth/sign_in -X POST {"success":false,"errors":["ログイン用の認証情報が正しくありません。再度お試しください。"]} パラメータあり % curl http://localhost:3000/users/auth/sign_in -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com", "password":"abc12345"}' {"data":{"email":"test@mydomain.com","id":31,"image":{"url":null,"mini":{"url":null},"small":{"url":null},"medium":{"url":null},"large":{"url":null},"xlarge":{"url":null}},"uid":"test@mydomain.com","code":"cdba39e2e8b5818d0cce1faceb5bce9a","name":"test","destroy_requested_at":null,"destroy_schedule_at":null,"provider":"email","allow_password_change":false,"nickname":null}} ヘッダも確認 % curl -i http://localhost:3000/users/auth/sign_in -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com", "password":"abc12345"}' 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 Content-Type: application/json; charset=utf-8 Vary: Accept access-token: svGZmaHKSYC26oiv9hU80A token-type: Bearer client: vb4ppkVwoV8B-WQqtSHCmQ expiry: 1629689672 uid: test@mydomain.com ETag: W/"3cb4ef623805dd83870a36bd6f22bab4" Cache-Control: max-age=0, private, must-revalidate X-Request-Id: d9f1ff0c-b45c-4301-b3ef-6ae807d26d9d X-Runtime: 0.381984 Transfer-Encoding: chunked
DBも確認 % rails db > SELECT tokens FROM users WHERE uid = 'test@mydomain.com'\G tokens: {"vb4ppkVwoV8B-WQqtSHCmQ":{"token":"$2a$10$qTUCoHNMqXgug2qHa5lvPO0JB1Hcquf5e5CSw12fj5ULIGqHmVAmG","expiry":1629689672,"last_token":"$2a$10$3gXTOEcW6.spZetO9yQUxuhJ8T2hXI14GSAMQMYMSvnXdcW0X7k8C","updated_at":"2021-08-09T12:34:32+09:00"}}
ヘッダのuid, client, access-tokenを使う事になりそう。
access-tokenは暗号化してDBに保存している。
デフォルトは10個までで、古い方から消えて行く。
同時ログイン数を変えたい場合は下記を変更すれば良さそう。
config/initializers/devise_token_auth.rb
# config.max_number_of_devices = 10
ログアウト(処理)
destroy_users_user_session DELETE /users/auth/sign_out(.:format) users/auth/sessions#destroy
パラメータなし % curl http://localhost:3000/users/auth/sign_out -X DELETE {"success":false,"errors":["ユーザーが見つからないか、ログインしていません。"]} パラメータあり % curl http://localhost:3000/users/auth/sign_out -X DELETE -H "Content-Type: application/json" -d '{ "uid":"test@mydomain.com", "client":"vb4ppkVwoV8B-WQqtSHCmQ", "access-token":"svGZmaHKSYC26oiv9hU80A"}' {"success":true} ヘッダ指定(こっちの方が良さそう) % curl http://localhost:3000/users/auth/sign_out -X DELETE \ -H "uid: test@mydomain.com" \ -H "client: vb4ppkVwoV8B-WQqtSHCmQ" \ -H "access-token: svGZmaHKSYC26oiv9hU80A" {"success":true}
DBも確認 % rails db > SELECT tokens FROM users WHERE uid = 'test@mydomain.com'\G tokens: NULL
想定通り、tokensから消えました。(複数ある場合は対象clientのみ消える)
パスワード再設定の流れ
パスワード再設定[メール送信](処理)
POST /users/auth/password(.:format) users/auth/passwords#create
パラメータなし % curl http://localhost:3000/users/auth/password -X POST {"success":false,"errors":["リダイレクト URL が与えられていません。"]} リダイレクトURL追加 % curl http://localhost:3000/users/auth/password -X POST -H "Content-Type: application/json" -d '{ "redirect_url":"http://localhost:5000/password"}' {"success":false,"errors":["メールアドレスが与えられていません。"]} メールアドレス追加 % curl http://localhost:3000/users/auth/password -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com", "redirect_url":"http://localhost:5000/password"}' {"success":true,"message":"'test@mydomain.com' にパスワードリセットの案内が送信されました。"}
メール送信もOK。但し、URLのパスが既存のまま(メールアドレス確認と同様)
メールのパスワード再設定URLを変更したい
メールアドレス確認と同様に、edit_password_urlが変わっている。
同様にパラメータも増えている為、修正が必要。
app/views/users/mailer/reset_password_instructions.html.erb
+ <% if message['redirect-url'].to_s.present? %> + <%= link_to 'パスワード再設定', edit_users_user_password_url(reset_password_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url'].to_s) %><br/> + <% else %> <%= link_to 'パスワード再設定', edit_password_url(@resource, reset_password_token: @token) %><br/> + <% end %>
app/views/users/mailer/reset_password_instructions.text.erb
+ <% if message['redirect-url'].to_s.present? %> + <%= edit_users_user_password_url(reset_password_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url'].to_s) %> + <% else %> <%= edit_password_url(@resource, reset_password_token: @token) %> + <% end %>
デフォルトURLを設定する?
default_confirm_success_urlと同様にdefault_password_reset_urlを設定できる。
同様にredirect_urlを指定しなくても、認証URLのパラメータにURLが追加される。
ただ、バックエンドがフロントを意識する事になり、自由に変えられなくなるので、無くて良いかも。設定するなら、こんな感じ。
config/initializers/devise_token_auth.rb
config.default_password_reset_url = 'http://localhost:5000/password'
ホワイトリストは効いているか?
% curl http://localhost:3000/users/auth/password -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com", "redirect_url":"http://badsite.com/"}' {"success":false,"errors":["'http://badsite.com/' へのリダイレクトは許可されていません。"],"status":"error","data":null}
OK。ホワイトリストが効いている。
パスワード再設定
GET /users/auth/password/edit(.:format) users/auth/passwords#edit
メールのURLからアクセスするので、ブラウザで
Tokenなし http://localhost:3000/users/auth/password/edit -> {"success":false,"errors":["リダイレクト URL が与えられていません。"]} リダイレクトURL追加と存在しないToken http://localhost:3000/users/auth/password/edit?redirect_url=http://localhost:5000/password http://localhost:3000/users/auth/password/edit?reset_password_token=not&redirect_url=http://localhost:5000/password -> 404: Routing Error 存在するToken http://localhost:3000/users/auth/password/edit?reset_password_token=9BzztrRyJH-e35xQ_ozq&redirect_url=http://localhost:5000/password -> Redirected to http://localhost:5000/password?access-token=czByTmxt0G87Z0Q2A7lMsg&client=0Rf2-UlCPpmpXhYdjeigHA&client_id=0Rf2-UlCPpmpXhYdjeigHA&config=&expiry=1629708772&reset_password=true&token=czByTmxt0G87Z0Q2A7lMsg&uid=test%40mydomain.com configを追加してみる http://localhost:3000/users/auth/password/edit?reset_password_token=9BzztrRyJH-e35xQ_ozq&redirect_url=http://localhost:5000/password&config=front -> Redirected to http://localhost:5000/password?access-token=YanD9r-LcoFXi7tDbzX3wA&client=oFZ7DvrMIOqszEdm0y2-JQ&client_id=oFZ7DvrMIOqszEdm0y2-JQ&config=front&expiry=1629709173&reset_password=true&token=YanD9r-LcoFXi7tDbzX3wA&uid=test%40mydomain.com
リダイレクト先で、パスワードを入力させて、後続のAPIを投げれば良さそう。
uid, client(client_idと同じ値), access-token(tokenと同じ値), configはパラメータで渡した値が返る。今回は不要かな。
パスワード変更後に、もう一度、同じURLにアクセスすると、404: Routing Errorになってしまう。
失敗時に404は解りにくいので、エラーコードを付けてリダイレクトに変更したい。後日対応
パラメータの認証情報を消したい
reset_password_tokenを知っていれば、認証情報作れるので無くても良さそう。
この認証情報でログイン状態にできちゃう気がする。
ログイン状態にするとしても、パスワードを入力した後の方が良い。
config/initializers/devise_token_auth.rb に追加
config.require_client_password_reset_token = true
http://localhost:3000/users/auth/password/edit?reset_password_token=9BzztrRyJH-e35xQ_ozq&redirect_url=http://localhost:5000/password -> Redirected to http://localhost:5000/password?reset_password_token=9BzztrRyJH-e35xQ_ozq
OK。単にredirect_urlにreset_password_token付けてリダイレクトしているだけになった。
ホワイトリストは効いているか?
http://localhost:3000/users/auth/password/edit?reset_password_token=9BzztrRyJH-e35xQ_ozq&redirect_url=http://badsite.com/ {"success":false,"errors":["'http://badsite.com/' へのリダイレクトは許可されていません。"],"status":"error","data":null}
OK。ホワイトリストが効いている。
が、JSONがブラウザに表示されるのは変ですね。後日対応
パスワード再設定(処理)
PUT(PATCH) /users/auth/password(.:format) users/auth/passwords#update
パラメータなし % curl http://localhost:3000/users/auth/password -X PUT {"success":false,"errors":["Unauthorized"]}
require_client_password_reset_token = falseの場合
パラメータあり ※ここでログイン状態にしないと思うので、ヘッダの認証情報もパラメータで渡した方が良さそう。 % curl http://localhost:3000/users/auth/password -X PUT -H "Content-Type: application/json" \ -H "uid: test@mydomain.com" \ -H "client: oFZ7DvrMIOqszEdm0y2-JQ" \ -H "access-token: YanD9r-LcoFXi7tDbzX3wA" \ -d '{ "reset_password_token":"YanD9r-LcoFXi7tDbzX3wA", "password":"def67890", "password_confirmation":"def67890"}' {"success":true,"data":{"email":"test@mydomain.com","id":31,"image":{"url":null,"mini":{"url":null},"small":{"url":null},"medium":{"url":null},"large":{"url":null},"xlarge":{"url":null}},"uid":"test@mydomain.com","code":"cdba39e2e8b5818d0cce1faceb5bce9a","name":"test","destroy_requested_at":null,"destroy_schedule_at":null,"created_at":"2021-08-08T15:01:04.294+09:00","updated_at":"2021-08-09T18:31:47.702+09:00","provider":"email","allow_password_change":false,"nickname":null},"message":"パスワードの更新に成功しました。"}
メール送信もOK
require_client_password_reset_token = trueの場合(今回はこっち)
パラメータあり。ヘッダも確認 % curl -i http://localhost:3000/users/auth/password -X PUT -H "Content-Type: application/json" -d '{ "reset_password_token":"YanD9r-LcoFXi7tDbzX3wA", "password":"def67890", "password_confirmation":"def67890"}' access-token: YMBuiXjq_9rRxIEmnmJT7g client: uSDHrLz4N5PWUFbPO5rXmw uid: test@mydomain.com {"success":true,"data":{"email":"test@mydomain.com","id":31,"image":{"url":null,"mini":{"url":null},"small":{"url":null},"medium":{"url":null},"large":{"url":null},"xlarge":{"url":null}},"uid":"test@mydomain.com","code":"cdba39e2e8b5818d0cce1faceb5bce9a","name":"test","destroy_requested_at":null,"destroy_schedule_at":null,"created_at":"2021-08-08T15:01:04.294+09:00","updated_at":"2021-08-09T18:31:47.702+09:00","provider":"email","allow_password_change":false,"nickname":null},"message":"パスワードの更新に成功しました。"}
メール送信もOK
ヘッダの認証情報を使って、ログイン状態にできそう。
失敗時にUnauthorizedは解りにくいので、エラーコードを付けてリダイレクトに変更したい。パラメータが多くてもエラーになる。後日対応
ActiveModel::UnknownAttributeError in Users::Auth::PasswordsController#update
アカウントロック解除の流れ
アカウントロック解除[メール再送](処理)
POST /users/auth/unlock(.:format) users/auth/unlocks#create
ロック状態を作る % rails c > user = User.find_by(email: 'test@mydomain.com') > user.unlock_token = Devise.token_generator.digest(self, :unlock_token, '1234567890') > user.locked_at = Time.current > user.save => true
パラメータなし % curl http://localhost:3000/users/auth/unlock -X POST {"success":false,"errors":["translation missing: ja.devise_token_auth.unlocks.missing_email"]} {"success":false,"errors":["メールアドレスが与えられていません。"]} メールアドレス追加 % curl http://localhost:3000/users/auth/unlock -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com"}' {"success":true,"message":"translation missing: ja.devise_token_auth.unlocks.sended"} {"success":true,"message":"'test@mydomain.com' にアカウントロック解除の案内が送信されました。"}
未ロック(メールのURLをクリック後)に再度、curl投げるとまた送信されてしまう。後日対応
(locked_atはNULLのままだが、unlock_tokenが設定される)
メッセージを日本語化したい
confirmationsと同じく、unlocksも不足しているので追加
config/locales/devise_token_auth.ja.yml に追加
unlocks: missing_email: 'メールアドレスが与えられていません。' sended_paranoid: 'ユーザーが見つかりません。' # Tips: user_not_foundで、config.paranoid = trueの場合 user_not_found: 'ユーザーが見つかりません。' sended: "'%{email}' にアカウントロック解除の案内が送信されました。"
メールのアカウントロック解除URLを変更したい
メールアドレス確認やパスワード再設定と同様に、unlock_urlが変わっている。
同様にパラメータも増えている為、修正が必要。
ソースの中にはなかったけど、他と同様にredirect-urlも追加。
そう言えば、リクエストパラメータにredirect_urlがなかった。嫌な予感。
app/views/users/mailer/unlock_instructions.html.erb
+ <% if message['redirect-url'].to_s.present? %> + <%= link_to 'アカウントロック解除', users_user_unlock_url(unlock_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url'].to_s) %><br/> + <% else %> <%= link_to 'アカウントロック解除', unlock_url(@resource, unlock_token: @token) %><br/> + <% end %>
app/views/users/mailer/unlock_instructions.text.erb
+ <% if message['redirect-url'].to_s.present? %> + <%= users_user_unlock_url(unlock_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url'].to_s) %> + <% else %> <%= unlock_url(@resource, unlock_token: @token) %> + <% end %>
動作確認
curl http://localhost:3000/users/auth/unlock -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com", "redirect_url":"http://localhost:5000/"}' {"success":true,"message":"'test@mydomain.com' にアカウントロック解除の案内が送信されました。"}
メールのURLが変わらない。redirect_urlがMailerに渡されていないっぽい。
http://localhost:3000/users/unlock?unlock_token=Dx4vZeeY68eJKmx7Ex5u
ソースを見ると確かにない。
gems/devise_token_auth-1.2.0/app/controllers/devise_token_auth/unlocks_controller.rb
@resource.send_unlock_instructions( email: @email, provider: 'email', client_config: params[:config_name] )
参考までにパスワード再設定のソース
gems/devise_token_auth-1.2.0/app/controllers/devise_token_auth/passwords_controller.rb
@resource.send_reset_password_instructions( email: @email, provider: 'email', redirect_url: @redirect_url, client_config: params[:config_name] )
redirect_urlに対応したい
passwords_controller.rbを参考にoverrideして修正する。
app/controllers/users/auth/unlocks_controller.rb に追加
before_action :validate_redirect_url_param, only: :create # POST /users/auth/unlock アカウントロック解除[メール再送](処理) def create return render_create_error_missing_email unless resource_params[:email] @email = get_case_insensitive_field_from_resource_params(:email) @resource = find_resource(:email, @email) if @resource yield @resource if block_given? @resource.send_unlock_instructions( email: @email, provider: 'email', redirect_url: @redirect_url, # Tips: 追加 client_config: params[:config_name] ) if @resource.errors.empty? render_create_success else render_create_error @resource.errors end else render_not_found_error end end private def validate_redirect_url_param # give redirect value from params priority @redirect_url = params.fetch( :redirect_url, Settings['default_unlock_success_url'] ) return render_create_error_missing_redirect_url unless @redirect_url return render_error_not_allowed_redirect_url if blacklisted_redirect_url?(@redirect_url) end def render_create_error_missing_redirect_url render_error(401, I18n.t('devise_token_auth.unlocks.missing_redirect_url')) end def render_error_not_allowed_redirect_url response = { status: 'error', data: resource_data } message = I18n.t('devise_token_auth.unlocks.not_allowed_redirect_url', redirect_url: @redirect_url) render_error(422, message, response) end
config/locales/devise_token_auth.ja.yml に追加
missing_redirect_url: "リダイレクト URL が与えられていません。" not_allowed_redirect_url: "'%{redirect_url}' へのリダイレクトは許可されていません。"
config/settings/development.yml に追加(デフォルトを使いたくないので空)
default_unlock_success_url: ''
動作確認
リダイレクトURLなし % curl http://localhost:3000/users/auth/unlock -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com"}' {"success":false,"errors":["リダイレクト URL が与えられていません。"]} リダイレクトURLあり % curl http://localhost:3000/users/auth/unlock -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com", "redirect_url":"http://localhost:5000/sign_in"}' {"success":true,"message":"'test@mydomain.com' にアカウントロック解除の案内が送信されました。"} ホワイトリスト確認 % curl http://localhost:3000/users/auth/unlock -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com", "redirect_url":"http://badsite.com/"}' {"success":false,"errors":["'http://badsite.com/' へのリダイレクトは許可されていません。"],"status":"error","data":null}
OK。メールのURLにリダイレクトURLも入った。ホワイトリストもOK
http://localhost:3000/users/auth/unlock?config=default&redirect_url=http://localhost:5000/sign_in&unlock_token=oochp18HeWhg1K_98vLy
アカウントロック解除(処理)
GET /users/auth/unlock(.:format) users/auth/unlocks#show
メールのURLからアクセスするので、ブラウザで
Tokenなしと不正 http://localhost:3000/users/auth/unlock http://localhost:3000/users/auth/unlock?unlock_token=not -> 404: Routing Error 存在するToken http://localhost:3000/users/auth/unlock?unlock_token=f_zgskXhNqz-np-4wyb5 -> Redirected to http://localhost:3000:///?access-token=f_zgskXhNqz-np-4wyb5&client=F9BSxyoqxrulQIt1Y6hm4g&client_id=F9BSxyoqxrulQIt1Y6hm4g&config=&expiry=1629764008&token=f_zgskXhNqz-np-4wyb5&uid=test%40mydomain.com&unlock=true リダイレクトURLを追加 http://localhost:3000/users/auth/unlock?unlock_token=f_zgskXhNqz-np-4wyb5&redirect_url=http://localhost:5000/sign_in -> Redirected to http://localhost:3000:///?access-token=f_zgskXhNqz-np-4wyb5&client=F9BSxyoqxrulQIt1Y6hm4g&client_id=F9BSxyoqxrulQIt1Y6hm4g&config=&expiry=1629764008&token=f_zgskXhNqz-np-4wyb5&uid=test%40mydomain.com&unlock=true
もう一度、同じURLにアクセスすると、404: Routing Errorになってしまう。
失敗時に404は解りにくいので、エラーコードを付けてリダイレクトに変更したい。後日対応
そもそも、リダイレクト先が変だ。
ソース見ると、TODOで’/’になっている。パラメータでも渡せなそう。
gems/devise_token_auth-1.2.0/app/controllers/devise_token_auth/unlocks_controller.rb
redirect_to(@resource.build_auth_url(after_unlock_path_for(@resource), redirect_headers)) private def after_unlock_path_for(resource) #TODO: This should probably be a configuration option at the very least. '/' end
リダイレクト先とパラメータを修正したい
リダイレクト先をoverrideして修正する。
ついで、access-token, client, uidがパラメータの付いちゃうので削除する。
パスワード入力なしに、ログイン状態に出来ちゃいそうな気がするので、危ない気がする。
app/controllers/users/auth/unlocks_controller.rb を変更・追加
- before_action :validate_redirect_url_param, only: :create + before_action :validate_redirect_url_param, only: %i[create show]
# GET /users/auth/unlock アカウントロック解除(処理) def show @resource = resource_class.unlock_access_by_token(params[:unlock_token]) if @resource.persisted? @resource.save! yield @resource if block_given? redirect_header_options = { unlock: true } redirect_to DeviseTokenAuth::Url.generate(@redirect_url, redirect_header_options) # Tips: 変更 else render_show_error end end
動作確認
リダイレクトURLなし http://localhost:3000/users/auth/unlock?unlock_token=7BLQ_Lx_DGuEV7A5vNkB {"success":false,"errors":["'' へのリダイレクトは許可されていません。"],"status":"error","data":null} ホワイトリストにないURL http://localhost:3000/users/auth/unlock?config=default&redirect_url=http://badsite.com/&unlock_token=7BLQ_Lx_DGuEV7A5vNkB {"success":false,"errors":["'http://badsite.com/' へのリダイレクトは許可されていません。"],"status":"error","data":null} ホワイトリストにあるURL http://localhost:3000/users/auth/unlock?config=default&redirect_url=http://localhost:5000/sign_in&unlock_token=7BLQ_Lx_DGuEV7A5vNkB -> Redirected to http://localhost:5000/sign_in?unlock=true
OK。ホワイトリストも効くようにしました。
が、JSONがブラウザに表示されるのは変ですね。後日対応
更新と削除
登録情報変更(処理)
PUT(PATCH) /users/auth(.:format) users/auth/registrations#update
パラメータなし % curl http://localhost:3000/users/auth -X PUT {"success":false,"errors":["リクエストボディに適切なアカウント更新のデータを送信してください。"],"status":"error"} 不正なメールアドレス % curl http://localhost:3000/users/auth -X PUT -H "Content-Type: application/json" -d '{ "email":"test"}' {"success":false,"errors":["ユーザーが見つかりません。"],"status":"error"} ログインして認証情報取得 % curl -i http://localhost:3000/users/auth/sign_in -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com", "password":"def67890"}' access-token: Aqi61VuvbKuyB8_nPks7cw client: sqWp9gVIhoXVEFwhbth6Ug uid: test@mydomain.com 認証情報をセットしてリクエスト % curl -i http://localhost:3000/users/auth -X PUT -H "Content-Type: application/json" \ -H "uid: test@mydomain.com" \ -H "client: sqWp9gVIhoXVEFwhbth6Ug" \ -H "access-token: Aqi61VuvbKuyB8_nPks7cw" \ -d '{ "email":"test"}' access-token: kS_f-e5o5b7Thj27TlPrqg {"status":"error","errors":{"email":["は有効ではありません"],"full_messages":["メールアドレス は有効ではありません"]}} メールアドレス・パスワード変更。リクエスト毎にaccess-tokenが変わる % curl -i http://localhost:3000/users/auth -X PUT -H "Content-Type: application/json" \ -H "uid: test@mydomain.com" \ -H "client: sqWp9gVIhoXVEFwhbth6Ug" \ -H "access-token: kS_f-e5o5b7Thj27TlPrqg" \ -d '{ "email":"new@mydomain.com", "password":"abc12345"}' access-token: Pgc6XfMZtLKmsSFvABquMA {"status":"success","data":{"email":"test@mydomain.com","id":31,"image":{"url":null,"mini":{"url":null},"small":{"url":null},"medium":{"url":null},"large":{"url":null},"xlarge":{"url":null}},"code":"cdba39e2e8b5818d0cce1faceb5bce9a","name":"test","destroy_requested_at":null,"destroy_schedule_at":null,"created_at":"2021-08-08T15:01:04.294+09:00","updated_at":"2021-08-10T14:21:43.888+09:00","provider":"email","uid":"test@mydomain.com","allow_password_change":false,"nickname":null}}
メール送信(パスワード変更完了とメールアドレス確認)もOK
http://localhost:3000/users/auth/confirmation?confirmation_token=SoFrSXmzSEzrysa2vSkb > Redirected to http://localhost:3000/users/auth/sign_in?account_confirmation_success=true % rails db > SELECT * FROM users WHERE email = "new@mydomain.com"\G email: new@mydomain.com uid: new@mydomain.com
メール認証した後、uidも変わる。
フロント側でuidをローカルストレージに保存しておいて、リダイレクト戻って来たらuid書き換えれば、ログイン状態継続できそう。
アカウント削除(処理)
DELETE /users/auth(.:format) users/auth/registrations#destroy
認証情報なし % curl http://localhost:3000/users/auth -X DELETE {"success":false,"errors":["削除するアカウントが見つかりません。"],"status":"error"} 認証情報あり % curl http://localhost:3000/users/auth" \ -H "uid: new@mydomain.com" \ -H "client: sqWp9gVIhoXVEFwhbth6Ug" \ -H "access-token: Pgc6XfMZtLKmsSFvABquMA" -X DELETE {"status":"success","message":"'new@mydomain.com' のアカウントは削除されました。"}
残りのrouteを確認(トークン検証等)
トークン検証(処理)
GET /users/auth/validate_token(.:format) users/auth/token_validations#validate_token
パラメータなし % curl -i http://localhost:3000/users/auth/validate_token -X GET {"success":false,"errors":["ログイン用の認証情報が正しくありません。"]} ログインして認証情報取得 % curl -i http://localhost:3000/users/auth/sign_in -X POST -H "Content-Type: application/json" -d '{ "email":"test@mydomain.com", "password":"abc12345"}' access-token: YKsCcq7kW_cp7VkoEOudzw client: XsZDCpczK6q3MIwxet9uag uid: test@mydomain.com 認証情報をセットしてリクエスト % curl -i http://localhost:3000/users/auth/validate_token -X GET \ -H "uid: test@mydomain.com" \ -H "client: XsZDCpczK6q3MIwxet9uag" \ -H "access-token: YKsCcq7kW_cp7VkoEOudzw" access-token: DIWtv1FxZ9pA7285u358Wg {"success":true,"data":{"id":32,"code":"0586380093e9665d1cefb044d3cbeef1","image":{"url":null,"mini":{"url":null},"small":{"url":null},"medium":{"url":null},"large":{"url":null},"xlarge":{"url":null}},"name":"test","email":"test@mydomain.com","destroy_requested_at":null,"destroy_schedule_at":null,"provider":"email","uid":"test@mydomain.com","allow_password_change":false,"nickname":null}}
不要なrouteを削る
GET /users/auth/sign_in(.:format) users/auth/sessions#new
-> {“success”:false,”errors”:[“/sign_in に GET はサポートされていません。POST をお使いください。”]}
GET /users/auth/password/new(.:format) users/auth/passwords#new
GET /users/auth/cancel(.:format) users/auth/registrations#cancel
GET /users/auth/sign_up(.:format) users/auth/registrations#new
GET /users/auth/edit(.:format) users/auth/registrations#edit
GET /users/auth/confirmation/new(.:format) users/auth/confirmations#new
GET /users/auth/unlock/new(.:format) users/auth/unlocks#new
-> 404: Unknown action
長くなってきたので、次回記載します。
見通しが良くなってきたので、この後はテスト駆動で作れそう!
まとめ
ログインしたら、uid, client, access-tokenをローカルストレージ等に保存して、
リクエスト毎にaccess-tokenを書き換えれば、セキュアにセッションを継続できる。
アカウントロック解除のリダイレクト先が指定できない。(上記で対応済み)
また、access-token, client, uidも返却されるので危険そう。(上記で対応済み)
(アカウントロック解除トークンを知られなければ大丈夫そうだけど)
メールアドレス変更で、uidが変わるので、ローカルストレージ等に保存しておいて、
リダイレクトで受け取った後に、uid書き換えないとログイン状態を維持できない。
ユースケースにもよりますが、redirect_urlにホワイトリスト制限を付けるか、CORSで制限した方が良い。(今回はホワイトリストを採用し、デフォルトURLは未使用にしました)
翻訳ファイルが一部不足しているので、追加した方が良い。(上記で対応済み)
次回は、後日対応と記載した部分をテスト駆動で対応して行きます。
エラーにした方が良い部分や、解りやすいエラーメッセージ返したり、ブラウザにJSON表示されないようにリダイレクトで戻したり。
今回のコミット内容
※ActionMailer Previewの修正も行なっています。
https://dev.azure.com/nightonly/rails-app-origin/_git/rails-app-origin/commit/91ed1f54881f4271ff460897613d794dfaff9756
“Devise Token Authの挙動を確認してみた” に対して2件のコメントがあります。