Handle Password and Email Changes in Your Rails API
Đây là phần 2 về vấn đề xác thực bằng cách sử dụng JWT. Bạn có thể xem phần 1 ở đây. Trong bài viết trước chúng ta đã thấy được tổng quát về JWT, cơ chế xác thực khác nhau, và các xác thực cơ bản API, giống như đăng ký, xác nhận và đăng nhập. Trong phần này, chúng ta sẽ xem các phần tiếp theo của ...
Đây là phần 2 về vấn đề xác thực bằng cách sử dụng JWT. Bạn có thể xem phần 1 ở đây. Trong bài viết trước chúng ta đã thấy được tổng quát về JWT, cơ chế xác thực khác nhau, và các xác thực cơ bản API, giống như đăng ký, xác nhận và đăng nhập. Trong phần này, chúng ta sẽ xem các phần tiếp theo của API như pasword (reset và thay đổi) và cập nhật email.
Trong phần này sẽ là nhiều hơn một bài hướng dẫn JWT. Mục tiêu chính của nó là để xem làm thế nào để xây dựng giải quyết xác thực tùy theo cách riêng của bạn từ đầu và JWT chỉ là phương thức chúng ta chọn để sử dụng.
Chúng ta sẽ tiếp tục xây dựng trên ứng dụng ví dụ trong phần đầu mà chúng ta đã phát triển. Bạn có thể tìm thấy nó ở đây.
1. Password
API Chúng ta tìm hiểu ở đây lần lượt là quên password. Luồng sinh một password reset token cùng với một endpoint cho user để hợp lệ hóa token. Endpoint này được gọi khi user click vào link thiết lập lại password gửi cho họ qua email. Endpoint cuối là để cuối cùng đã thay đổi mật khẩu.
1.1. Forgot Password
Các endpoint quên password sinh một token thiết lập lại password, lưu nó trong database, và gửi một email đến user. Điều này cũng tương tự module hướng dẫn confirm như trong phần 1 chúng ta đã tìm hiểu. Hãy bắt đầu bằng cách thêm các column cần thiết cho chức năng reset password.
rails g migration AddPasswordResetColumnsToUser
Và trong file migration, thêm dòng sau vào:
add_column :users, :reset_password_token, :string add_column :users, :reset_password_sent_at, :datetime
Hai cột này là đủ cho mục đích này. reset_password_token sẽ lưu trữ các token chúng tôi tạo ra và reset_password_sent_at lưu trữ0 thời gian token được gửi với mục đích hạn sử dụng. Hãy thêm các endpoint bây giờ. Bắt đầu bằng cách tạo controller password:
rails g controller passwords
Thêm routes vào file config/routes.rb:
post 'password/forgot', to: 'password#forgot' post 'password/reset', to: 'password#reset'
Bây giờ hãy thêm action tương ứng cho route tương ứng vào file controllers/password_controller.rb:
... def forgot if params[:email].blank? return render json: {error: 'Email not present'} end user = User.find_by(email: email.downcase) if user.present? && user.confirmed_at? user.generate_password_token! # SEND EMAIL HERE render json: {status: 'ok'}, status: :ok else render json: {error: ['Email address not found. Please check and try again.']}, status: :not_found end end def reset token = params[:token].to_s if params[:email].blank? return render json: {error: 'Token not present'} end user = User.find_by(reset_password_token: token) if user.present? && user.password_token_valid? if user.reset_password!(params[:password]) render json: {status: 'ok'}, status: :ok else render json: {error: user.errors.full_messages}, status: :unprocessable_entity end else render json: {error: ['Link not valid or expired. Try generating a new link.']}, status: :not_found end end ...
Chúng ta sẽ xem qua đoạn code trên. Trong action forgot, lấy email trong request gửi lên và tìm user. Nếu tìm thấy user và đã được confirmed, gọi hàm generate_password_token trong model user và gửi email. Phần gửi email chúng ta ko tìm hiểu ở đây, nhưng phải chắc chắn bao gồm password_reset_token của user trong email. Trong action reset, lấy token được gửi trong request và xác nhận nó hợp lệ qua hàm password_token_valid? và thiết lập lại password thông qua reset_password. Giờ chúng ta sẽ thêm các method còn thiếu vào model user:
... def generate_password_token! self.reset_password_token = generate_token self.reset_password_sent_at = Time.now.utc save! end def password_token_valid? (self.reset_password_sent_at + 4.hours) > Time.now.utc end def reset_password! password self.reset_password_token = nil self.password = password save! end private def generate_token SecureRandom.hex(10) end ...
Trong phương thức generate_password_token! chúng ta sinh một token sử dụng phương thức generate_token và lưu trữ nó trong column reset_password_token, và cũng thiết lập reset_password_sent_at tại thời điểm hiện tại. Trong phương thức password_token_valid?, xác minh các token được gửi trong vòng 4 giờ tức là hết hạn reset password. Bạn được tự do thay đổi nó, tuy nhiên bạn sẽ thấy nó là phù hợp. Phương thức reset_password! cập nhật password mới của user và vô hiệu hóa các token reset.
Reset password đã thực hiện xong. Bạn có thể test nó bằng cách gửi request post đến /passwords/forgot với email trong body và /passwords/reset với một password mới và token trong body.
1.2. Update Password
Để thêm update password, hãy thêm route tương ứng vào file routes.rb:
put 'password/update', to: 'password#update'
Route ở trên tương ứng với action trong PasswordsController:
def update if !params[:password].present? render json: {error: 'Password not present'}, status: :unprocessable_entity return end if current_user.reset_password(params[:password]) render json: {status: 'ok'}, status: :ok else render json: {errors: current_user.errors.full_messages}, status: :unprocessable_entity end end
Action update password khá là đơn giản. Lấy password từ tham số gửi lên và lưu nó vào DB sử dụng phương thức reset_password mà chúng ta đã khai báo trước đó trong model User. Bạn có thể test URL update password bằng cách gửi một request PUT đến /password/update với password mới trong body. Tiếp theo chúng ta sẽ đi tìm hiểu chức năng tiếp theo, update email.
2. Email Update
Update Email cho phép user update email của chính họ trên tài khoản của mình. Khi có yêu cầu, chúng ta nên kiểm tra nếu email đã được sử dụng bởi bất cứ user nào khác. Nếu email là OK, lưu trữ nó và gửi một email xác nhận đến email mới. Sau khi xác nhận, chúng ta sẽ thay thế email chính với email mới và làm sạch trong các tokens.
Vì vậy, có hai API trong tổng số: Một để thực hiện một yêu cầu cập nhật email, một để thực sự cập nhật email.
Bắt đầu bằng cách tạo migration thêm một column cần thiết để hỗ trợ module này:
rails g migration AddUnconfirmedEmailTouser
Thêm nội dung và file vừa tạo và chạy rake db:migrate:
add_column :users, :unconfirmed_email, :string
2.1. Update
Bây giờ, hãy update route cho 2 endpoint. Và thêm chúng vào file config/routes.rb:
... resources :users, only: [:create, :update] do collection do post 'email_update' ...
Thêm action tương ứng vào UsersController:
def update if current_user.update_new_email!(@new_email) # SEND EMAIL HERE render json: { status: 'Email Confirmation has been sent to your new Email.' }, status: :ok else render json: { errors: current_user.errors.values.flatten.compact }, status: :bad_request end end
Cũng thêm một before_action để làm kiểm tra trên email mới, và thêm dòng này ở trên đầu class controller user với phương thức private:
class UsersController < ApplicationController before_action :validate_email_update, only: :update ... ... private def validate_email_update @new_email = params[:email].to_s.downcase if @new_email.blank? return render json: { status: 'Email cannot be blank' }, status: :bad_request end if @new_email == current_user.email return render json: { status: 'Current Email and New email cannot be the same' }, status: :bad_request end if User.email_used?(@new_email) return render json: { error: 'Email is already in use.'] }, status: :unprocessable_entity end end ...
Ở đây chúng ta kiểm tra nếu email yêu cầu đã được sử dung và nếu email là giống với tài khoản nào đã có. Nếu mọi thứ đều ổn thì gọi **update_new_email!**và gửi email. Chú ý rằng, email phải gửi đến unconfirmed_email của user thay vì email chính của họ. Chúng ta đã sử dụng thêm 2 method model mới, giờ sẽ đi định nghĩa chúng trong file models/user.rb:
def update_new_email!(email) self.unconfirmed_email = email self.generate_confirmation_instructions save end def self.email_used?(email) existing_user = find_by("email = ?", email) if existing_user.present? return true else waiting_for_confirmation = find_by("unconfirmed_email = ?", email) return waiting_for_confirmation.present? && waiting_for_confirmation.confirmation_token_valid? end end
Ở đây, trong email_used?, ngoài việc kiểm tra nếu email được sử dụng chính trong bất kỳ tài khoản nào, chúng ta cũng kiểm tra nếu nó được update và chờ đợi để confirm. Điều này có thể được loại bỏ tùy theo nhu cầu của bạn. Phương thức confirmation_token_valid? được thêm vào trong phần đầu của bài.
Bây giờ có thể kiểm tra route này bằng cách gửi một request POST đến /users/update với email trong body yêu cầu.
2.2. Email Update
Bây giờ hãy thêm action cho update email vào trong class UsersController:
def email_update token = params[:token].to_s user = User.find_by(confirmation_token: token) if !user || !user.confirmation_token_valid? render json: {error: 'The email link seems to be invalid / expired. Try requesting for a new one.'}, status: :not_found else user.update_new_email! render json: {status: 'Email updated successfully'}, status: :ok end end
Action này khá là đơn giản. Lấy user bởi token và xem nếu token là hợp lệ. Nếu vậy, cập nhật email và trả lời. Hãy thêm phương thức update_new_email! vào model user:
def update_new_email! self.email = self.unconfirmed_email self.unconfirmed_email = nil self.mark_as_confirmed! end
Ở đây chúng ta thay các email chính với email được update và thiết lập trường email đã update thành nil. Ngoài ra, gọi mark_as_confirmed! mà chúng ta thêm vào trong phần trước. Phương thức này làm vô hiệu các token xác nhận và thiết lập xác nhận theo giá trị. Hãy thử một yêu cầu POST đến /users/email_update với token email mà chúng ta đã tạo trong phần trước.
Tham khảo: https://www.sitepoint.com/handle-password-and-email-changes-in-your-rails-api/