Статьи

Аутентификация без пароля в Rails

Аутентификация является одним из ключевых элементов многих веб-приложений. Это каменная стена между приложением и его пользователями, поэтому важно, чтобы подход к аутентификации был безопасным и простым в использовании. Но что это за «аутентификация»? Это способ гарантировать, что только пользователи, авторизованные нашим приложением, могут использовать приложение. Как я уверен, вы знаете, что существует много способов аутентификации пользователя, таких как электронная почта / пароль, OpenID Connect, SAML, SSO и так далее.

Сегодня мы рассмотрим другой подход: аутентификация без пароля.

Что такое аутентификация без пароля?

Когда пользователь регистрируется на веб-сайте, приложение позволяет ему выбирать свои учетные данные, обычно имя пользователя или адрес электронной почты / пароль. Затем пользователь может в любое время ввести эти учетные данные для входа в приложение. Аутентификация без пароля в основном исключает часть пароля и использует только электронную почту для регистрации и входа в систему.

Как это работает, когда пользователь регистрируется в нашем приложении, отправляется электронное письмо для активации его учетной записи. Это позволяет нам проверить, принадлежит ли электронная почта пользователю. Теперь, когда у нас есть подтвержденное электронное письмо, в следующий раз, когда пользователь попытается войти в систему, мы отправим ему электронное письмо с токеном, который пользователь сможет использовать для входа в приложение. Как только пользователь нажимает на ссылку с токеном, приложение аутентифицирует пользователя.

Давайте начнем. Инициализируйте ваше приложение рельсов:

$ rails new passwordless $ cd passwordless 

Чтобы получить более ясное представление о том, что на самом деле происходит, мы не будем использовать какие-либо библиотеки или гемы для этого урока.

Создание модели

Начнем с создания модели, необходимой для нашего приложения. Мы будем называть эту модель User поскольку это пользователи, которых мы аутентифицируем. Но вы можете использовать все, что работает.

 $ rails g scaffold user fullname username:uniq email:uniq login_token token_generated_at:datetime $ rails db:create && rails db:migrate 

fullname username и username являются обязательными. Мы добавили username чтобы пользователи могли войти по email или через username . У нас также есть пара других столбцов, login_token и token_generated_at , которые в основном являются одноразовыми паролями, которые мы генерируем для наших пользователей, а также когда они были сгенерированы.

На уровне таблицы есть уникальное ограничение, но давайте также добавим валидации ActiveRecord для модели. Добавьте следующее в app / models / user.rb :

 validates :email, :username, uniqueness: true, presence: true 

Наряду с этим, давайте также добавим фильтр before для форматирования имени username и email перед сохранением записи. Эти проверки также должны быть в клиенте, но это дополнительная мера. Добавьте фильтр перед:

 ... before_save :format_email_username def format_email_username self.email = self.email.delete(' ').downcase self.username = self.username.delete(' ').downcase end 

Здесь мы в основном убираем пробелы в username и email и делаем его строчными, прежде чем сохранить его в базе данных.

Поскольку мы собираемся разрешить пользователям входить в систему по имени пользователя и электронной почте, давайте добавим вспомогательный метод, который извлекает запись пользователя на основе либо:

 ... def self.find_user_by(value) where(["username = :value OR email = :value", {value: value}]).first end 

Постановка на учет

Когда модель готова и готова, давайте продолжим и создадим файл нашего контроллера и необходимые маршруты. Прежде чем приступить к созданию контроллера регистрации, быстро создайте статическую страницу для отображения сообщений:

 $ rails g controller static home 

Добавьте приведенный ниже маршрут в config / rout.rb :

 root 'static#home' 

Наряду с этим, также добавьте следующую строку в ваш app / views / layouts / application.html.erb , который используется для отображения сообщений пользователю:

 ... <body> <p id="notice"><%= notice %></p> ... 

Создайте пользовательский контроллер:

 $ rails g controller users 

В config / rout.Rb добавьте следующие маршруты, которые мы будем использовать для регистрации пользователей:

 resources :users, only: [:create] get '/register', to: 'users#register' 

Теперь давайте добавим код в app / controllers / users_controller.rb, который соответствует маршрутам, объявленным выше:

 def register @user = User.new end def create @user = User.new(user_params) if @user.save redirect_to root_path, notice: 'Welcome! We have sent you the link to login to our app' else render :register end end private def user_params params.require(:user).permit(:fullname, :username, :email) end 

Теперь создайте файл представления для регистрации в app / views / users / register.html.erb и добавьте в него эту форму:

 <h1>Register</h1> <%= form_for(@user) do |f| %> <% if @user.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@user.errors.count, "error") %> prohibited this @user from being saved:</h2> <ul> <% @user.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :fullname %> <%= f.text_field :fullname %> </div> <div class="field"> <%= f.label :username %> <%= f.text_field :username %> </div> <div class="field"> <%= f.label :email %> <%= f.text_field :email %> </div> <div class="actions"> <%= f.submit 'Register' %> </div> <% end %> 

Отмечая особенное в этой форме. Это стандартная Rails, сгенерированная скаффолом форма, которая фиксирует fullname username , username и email для пользователя и отправляет его в нашу конечную точку create . Запустите сервер Rails и зайдите /register и увидите, что регистрация активна!

Ссылка для входа

Давайте перейдем к содержательной части приложения: отправка электронных писем с логином. По сути, когда новый пользователь регистрируется или когда он запрашивает вход в систему, мы должны отправить ему ссылку для входа с токеном. Когда ссылка нажата, приложение войдет в систему пользователя. Начните с добавления следующих вспомогательных методов в наш apps / models / user.rb для отправки электронных писем:

 ... def send_login_link generate_login_token template = 'login_link' UserMailer.send(template).deliver_now end def generate_login_token self.login_token = generate_token self.token_generated_at = Time.now.utc save! end def login_link "http://localhost:3000/auth?token=#{self.login_token}" end def login_token_expired? Time.now.utc > (self.token_generated_at + token_validity) end def expire_token! self.login_token = nil save! end private def generate_token SecureRandom.hex(10) end def token_validity 2.hours end end 

Пожалуйста, не зацикливайтесь на том, где живет код, который отправляет почту. Я стараюсь максимально снизить уровень шума и сосредоточиться на концепциях более высокого уровня. Я бы никогда не UserMailer , например, в модель, но это только для демонстрационных целей.

Итак, у нас есть метод send_login_link который мы вскоре будем использовать, чтобы отправить ссылку для входа в систему для пользователя. Прежде чем сохранить его в нашей базе данных, мы фактически хешируем его, используя BCrypt, что делает его более безопасным в случае взлома данных. Наряду с этим также добавьте гем ‘bcrypt’ в ваш Gemfile.

Как только мы сгенерируем токен для входа, отправьте его пользователю по электронной почте с помощью ActionMailer UserMailer . Настройка почтовых функций в этом учебном пособии пропущена, поскольку существует множество хороших учебных пособий, в которых объясняется, как их выполнять в соответствии с вашим поставщиком электронной почты. Просто убедитесь, что вы включили аргумент ссылки, который мы передаем UserMailer в метод `send_login_link`, в шаблон электронной почты, который вы отправляете пользователю.

login_link настроена с помощью URL- login_link localhost , но измените его соответственно для своего приложения. Кроме того, длительность token_validity установлена ​​на 2 часа, но вы можете изменить ее, очевидно. Наконец, добавьте эту строку в app / controllers / users_controller.rb create action сразу после строки @user.save :

 ... if @user.save @user.send_login_link ... 

Теперь, когда у нас есть необходимые вспомогательные методы, давайте добавим маршрут получения, чтобы обработать ссылку для входа в систему, которую мы отправляем в электронном письме.

Контроллер сессий

Начните с создания контроллера для сессии.

 $ rails g controller session auth 

Обновите файл config / rout.Rb , изменив get 'session/auth' на get '/auth/:user_id/:token', to: 'session#auth' . В сгенерированный файл session_controller.rb добавьте этот код:

 def auth token = params[:token].to_s user_id = params[:user_id] user = User.find_by(id: user_id) if !user || !user.valid_token? redirect_to root_path, notice: 'It seems your link is invalid. Try requesting for a new login link' elsif user.login_token_expired? redirect_to root_path, notice: 'Your login link has been expired. Try requesting for a new login link.' else sign_in_user(user) redirect_to root_path, notice: 'You have been signed in!' end end 

Используя вспомогательный метод, проверьте, является ли токен действительным или срок его действия истек. Если это не верно, перенаправьте на домашнюю страницу с соответствующими сообщениями. Есть вспомогательный метод, который мы использовали, sign_in_user , который мы должны создать. Откройте app / controllers / application_controller.rb и добавьте:

 def sign_in_user(user) user.expire_token! session[:email] = user.email end def current_user User.find_by(email: session[:email]) end 

Мы в основном истекаем токен пользователя и сохраняем электронную почту пользователя в сеансе. Также мы добавили вспомогательный метод для извлечения пользователя из сеанса. Функция без пароля готова, продолжайте и попробуйте зарегистрироваться для нового пользователя для проверки функциональности входа в систему.

Авторизоваться

В качестве последнего шага мы будем использовать наши вспомогательные методы для входа пользователя. Начните с добавления этих маршрутов в файл config / rout.rb :

 resources :session, only: [:new, :create] 

и добавьте приведенный ниже код в файл /app/controllers/session_controller.rb :

 def new end def create value = params[:value].to_s user = User.find_user_by(value) if !user redirect_to new_session_path, notice: "Uh oh! We couldn't find the username / email. Please try again." else user.send_login_link redirect_to root_path, notice: 'We have sent you the link to login to our app' end end 

Мы только что использовали send_login_link чтобы сделать тяжелую работу. Последний элемент приложения — форма входа в систему. Создайте файл app / views / session / new.html.erb и добавьте следующую форму:

 <%= form_tag "/session" do %> <label> Email / Username </label> <%= text_field_tag "value" %> <%= submit_tag "Login" %> <% end %> 

Это простая форма, которая делает работу за нас.

Вывод

С этим мы пришли к завершению урока. В наши дни логин без пароля действительно набирает обороты и предоставляет нашим пользователям менее отвлекающий и более удобный способ аутентификации. О, и это также обеспечивает меньше накладных расходов для всех участников. Я рекомендую вам попробовать подход для входа без пароля для вашего приложения, по крайней мере, в качестве дополнительного метода входа в систему. Это было бы началом!

Весь пример кода, используемый в этом руководстве, доступен на Github , не стесняйтесь раскошелиться и поиграть с ним. Я благодарю вас за чтение учебника и надеюсь, что он послужил вашим целям.