Это вторая часть нашей серии статей об аутентификации с нуля с использованием JWT. Первая часть серии может быть найдена здесь . В предыдущем уроке мы увидели краткий обзор JWT, различных механизмов аутентификации и набора базовых API аутентификации, таких как регистрация, подтверждение и вход в систему. В этой части мы увидим следующий набор API, таких как пароль (сброс и изменение) и обновление электронной почты.
Эта серия — больше, чем учебник JWT. Его главная цель — увидеть, как создать собственное решение для аутентификации с нуля, а JWT — это именно тот метод, который мы выбрали.
Мы продолжим работу над нашим примером приложения, которое мы разработали в первой части, которую можно найти здесь . Если вы хотите следовать, вы можете проверить ветку part-i
пароль
API, который мы рассмотрим здесь — это забытая последовательность паролей. Поток генерирует токен сброса пароля вместе с конечной точкой, чтобы пользователь мог проверить токен. Эта конечная точка вызывается, когда пользователь щелкает ссылку для сброса пароля, отправленную ему по электронной почте. Эта последняя конечная точка предназначена для окончательной смены пароля.
Забыл пароль
Конечная точка забытого пароля генерирует токен сброса пароля, сохраняет его в базе данных и отправляет электронное письмо пользователю. Это похоже на модуль инструкций подтверждения, который мы видели в первой части. Начнем с добавления столбцов, необходимых для сброса пароля. Бегать,
rails g migration AddPasswordResetColumnsToUser
И в сгенерированный файл миграции добавьте следующее:
add_column :users, :reset_password_token, :string
add_column :users, :reset_password_sent_at, :datetime
Этих двух столбцов достаточно для этой цели. reset_password_token
reset_password_sent_at
Давайте добавим конечные точки сейчас. Начнем с генерации пароля контроллера:
rails g controller passwords
Добавьте следующие маршруты в ваш файл config / rout.rb :
post 'password/forgot', to: 'password#forgot'
post 'password/reset', to: 'password#reset'
Теперь давайте добавим соответствующее действие для вышеупомянутых маршрутов в 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
...
Давай пройдемся по этому вопросу быстро. В forgot
Если пользователь найден и подтвержден, вызовите generate_password_token
Часть отправки электронной почты пропущена, но обязательно включите в нее password_reset_token
В действии reset
password_token_valid?
и сбросьте пароль с помощью reset_password
Эти методы еще не добавлены в пользовательскую модель, давайте сделаем это сейчас:
Добавьте следующие методы в models / user.rb :
...
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
...
В generate_password_token!
В методе мы генерируем токен с помощью метода generate_token
reset_password_token
reset_password_sent_at
В password_token_valid?
Убедитесь, что токен отправлен в течение последних 4 часов, что является истечением срока действия пароля. Вы можете изменить его, как считаете нужным. reset_password!
Метод обновляет новый пароль пользователя и аннулирует токен сброса.
Сброс пароля установлен. Вы можете проверить это, отправив почтовый запрос /passwords/forgot
/passwords/reset
Давайте добавим ссылку для обновления пароля сейчас.
Обновить пароль
Чтобы добавить пароль для обновления, добавьте маршрут в ваш файл маршрутов:
put 'password/update', to: 'password#update'
Вот соответствующее действие в 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
Действие по update
Получите пароль от параметра и сохраните его в БД, используя метод reset_password
Теперь вы можете проверить URL обновления пароля, отправив запрос PUT
/password/update
Давайте перейдем к следующей большой функциональности, Email Update.
Обновление электронной почты
Обновление по электронной почте позволяет пользователю обновить свою основную электронную почту в своей учетной записи. По запросу мы должны проверить, используется ли электронная почта любым другим пользователем. Если электронная почта в порядке, сохраните ее и отправьте письмо с подтверждением на новую электронную почту. После подтверждения мы заменим основной адрес электронной почты новым и удалим токен.
Итак, всего существует два API: один для отправки запроса на обновление электронной почты, второй для фактического обновления электронной почты. Давайте начнем.
Начните с миграции, чтобы добавить необходимый столбец для поддержки этого модуля. Генерация миграции:
rails g migration AddUnconfirmedEmailTouser
Добавьте следующий контент и запустите rake db:migrate
add_column :users, :unconfirmed_email, :string
Обновить
Теперь давайте обновим маршруты для этих двух конечных точек. Добавьте эти строки в config / rout.rb :
...
resources :users, only: [:create, :update] do
collection do
post 'email_update'
...
Добавьте соответствующие действия в 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
Также добавьте before_action
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
...
Здесь мы проверяем, используется ли запрошенная электронная почта, и совпадает ли электронная почта с той, что уже есть в учетной записи. Если все хорошо, звоните update_new_email!
и отправьте письмо. Обратите внимание, что электронное письмо должно быть отправлено пользователю unconfirmed_email
Мы использовали несколько новых методов модели, поэтому давайте определимся с ними. В 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
Здесь, в email_used?
Помимо проверки того, используется ли электронная почта главным образом в каких-либо учетных записях, мы также проверяем, обновляется ли она и ожидает подтверждения. Это может быть удалено в зависимости от ваших потребностей. confirmation_token_valid?
метод был добавлен в первой части этого урока.
Теперь вы можете проверить этот маршрут, отправив запрос POST
/users/update
email
Обновление электронной почты
Теперь давайте добавим действие для конечной точки обновления электронной почты. Добавьте этот код в 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
Это действие довольно простое. Получить пользователя по токену и посмотреть, является ли токен действительным. Если так, обновите электронную почту и ответьте. Давайте добавим update_new_email!
Метод для пользовательской модели:
def update_new_email!
self.email = self.unconfirmed_email
self.unconfirmed_email = nil
self.mark_as_confirmed!
end
Здесь мы заменяем основной адрес электронной почты обновленным адресом электронной почты и устанавливаем поле обновленного адреса электронной почты nil
Кроме того, вызовите mark_as_confirmed!
который мы добавили в предыдущей части серии. Этот метод аннулирует токен подтверждения и устанавливает подтвержденное значение. Конечная точка обновления электронной почты также работает. Попробуйте отправить запрос POST
/users/email_update
Вывод
На этом мы подошли к завершению нашего учебника из двух частей по аутентификации с нуля для Rails API. Напомним, что мы реализовали модули аутентификации, подтверждения, пароля и повторного подтверждения Devise. Не слишком потрепанный.
Код, используемый в этом руководстве, доступен здесь . Код, с которого начинается данное руководство, доступен в ветке part-i .
Я надеюсь, что это руководство помогло вам понять аутентификацию и развернуть собственную систему аутентификации. Спасибо за прочтение.