Password-Less Authentication in Rails

Share this article

Password-Less Authentication in Rails

Authentication is one of the key elements of many web applications. It is the stone wall between the application and its users, so it’s important that the authentication approach is secure and easy to use. But, what is this “authentication”? It’s a way of ensuring only users authorized by our application are allowed to use the application. As I am sure you know, there are many ways to authenticate a user, such as Email/Password, OpenID Connect, SAML, SSO, and so on.

Today, we’re going to take a look at another approach: Password-less authentication.

Key Takeaways

  • Password-less authentication in Rails eliminates the need for passwords, using only the user’s email to register and log in. This involves sending a unique token to the user’s email during registration and login, which the user clicks on to be authenticated.
  • The tutorial provides a step-by-step guide on how to implement password-less authentication in Rails. This includes creating a User model, setting up routes and controllers for registration and login, and creating helper methods for sending login links and generating tokens.
  • Password-less authentication can be more secure than traditional password-based authentication, as it eliminates the risk of password breaches. However, its security depends largely on the security of the user’s email account.
  • Despite its benefits, password-less authentication in Rails also has some drawbacks. If the user’s email account is compromised, the token can be misused. Therefore, it’s crucial to ensure the email system is secure and users are educated about securing their email accounts.

What is Password-less Authentication?

When a user registers for a website, the application allows the user to chose their credentials, usually a username or email/password. The user can then enter those credentials anytime to login to the application. Password-less authentication is basically eliminating the password part and using just the email to both register and login.

How this works is, when a user registers to our application, an email is sent to activate their account. This allows us to verify if the email belongs to the user. Now that we have a verified email, the next time that user tries to login, we will send them an email with the token for the user to use to sign into the app. Once the user clicks on the link with the token, the application will authenticate the user.

Let’s get started. Initialize your rails application:

$ rails new passwordless
$ cd passwordless

To get a clearer sense of what’s really happening, we won’t be using any libraries or gems for this tutorial.

Creating the Model

Let’s start with creating the model necessary for our application. We are going to call this model User since it’s users we are authenticating. But you are free to use anything that works.

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

The fullname and username are optional. We’ve added username to enable users to login via either email or username. We also have couple of other columns, login_token and token_generated_at, which are basically the one time password we generate for our users, along with when it was generated.

There’s a unique constraint at the table level, but let’s also add the ActiveRecord validations for the model. Add the following to the app/models/user.rb:

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

Along with this, let’s also add a before filter to format the username and email before saving the record. These validations should also be in the client, but this is an added measure. Add the before filter:

...
before_save :format_email_username

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

Here we basically strip the spaces in username and email and making it lowercase before saving it to the database.

Since we’re going to allow for users to login via username and email, let’s add a helper method that fetches the user record based on either:

...

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

Registration

With the model done and in place, let’s go ahead and create our controller file and the necessary routes. Before we go about creating the registration controller, quickly create a static page to show messages:

$ rails g controller static home

Add the below route to config/routes.rb:

root 'static#home'

Along with this, also add the following line to your app/views/layouts/application.html.erb, which is used to display messages to the user:

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

Generate a users controller:

$ rails g controller users

In config/routes.rb, add the following routes which we’ll use for user registration:

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

Now, let’s add the code in app/controllers/users_controller.rb that corresponds to the routes declared above:

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

Now, create the view file for registration under app/views/users/register.html.erb and add the this form to it:

<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 %>

Noting special in this form. This is a standard Rails, scaffole-generated form which captures the fullname, username, and email for the user and sends it to our create endpoint. Start the Rails server and head over to /register and see the registration is live now!

Let’s get to the meaty part of the application: sending login emails. Basically, when a new user registers or whenever they request to login, we’d have to send them a login link with a token. When the link is clicked, the app will login the user. Begin by adding the following helper methods to our apps/models/user.rb for sending emails:

...

  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

Please don’t get hung up on where the code that sends the mail lives. I am trying to keep the noise down as much as possible and focus on the higher-level concepts. I would never put a UserMailer, for example, in a model, but this is for demonstration purposes only.

So, we have the send_login_link method which we’ll make use of shortly to send the login link for a user. Before storing it to our database, we are actually hashing it using BCrypt which makes it more secure in case of a data breach. Along with this also add the gem ‘bcrypt’ to your Gemfile.

Once we generate the login token, send it to the user in an email using ActionMailer UserMailer. Setting up the mailing functionality is skipped in this tutorial since there are many good tutorials out there that explain how to do them according to your email provider. Just make sure you include the link argument that we pass to the UserMailer in `send_login_link` method in your email template that you send to the user.

The login_link is configured with a localhost url, but change it accordingly for your application. Also, the token_validity duration is set to 2 hours, but you are free to change it, obviously. Finally, add this line to the app/controllers/users_controller.rb create action right after the @user.save line:

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

Now that we have the necessary helper methods in place, let’s add the receiving route to handle the login link we send in the email.

Session Controller

Start by generating the controller for session.

$ rails g controller session auth

Update in the config/routes.rb file, changing get 'session/auth' to get '/auth/:user_id/:token', to: 'session#auth'. In the generated session_controller.rb file, add this code:

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

Using the helper method, check whether the token is a valid or if it’s expired. If it’s not valid, redirect to the home page with the appropriate messages. There is a helper method we have used, sign_in_user, which we have to create. Open up app/controllers/application_controller.rb and add:

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

def current_user
  User.find_by(email: session[:email])
end

We basically expire the token of the user and store the user’s email to the session. Also, we have added a helper method to retrieve the user from the session. The password-less functionality is ready, go ahead and try registering for a new user for testing the login functionality.

Login

As a final step, we’ll make use of our helper methods to do the user login. Start by adding these routes to the config/routes.rb file:

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

and add the below code to the /app/controllers/session_controller.rb file:

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

We have just made use of the send_login_link to do the heavy lifting. The final piece of the app is the login form. Create the file app/views/session/new.html.erb and add the following form:

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

It’s just a simple form that does the job for us.

Conclusion

With that, we have arrived at the conclusion of the tutorial. Password-less login is really picking up these days and it provides our users with a less distracting and more convenient way to authenticate. Oh, and it also provides less overhead for everyone involved. I encourage you to try the password-less login approach for your application, at least as a supplemental method for login. That would be a start!

All the sample code used in this tutorial is available on Github, feel free to fork and play with it. I thank you for reading the tutorial and I hope it served your purposes.

Frequently Asked Questions (FAQs) on Passwordless Authentication in Rails

How secure is passwordless authentication in Rails?

Passwordless authentication in Rails is considered secure because it eliminates the risk of password breaches. It uses a token-based system where a unique token is sent to the user’s email. This token is then used to authenticate the user. However, the security of this method largely depends on the security of the user’s email account. If the email account is compromised, the token can be misused. Therefore, it’s crucial to ensure the email system is secure and users are educated about the importance of securing their email accounts.

How does passwordless authentication work in Rails?

Passwordless authentication in Rails works by sending a unique token to the user’s email address. The user clicks on the link containing the token, which authenticates them and logs them into the system. This eliminates the need for users to remember complex passwords and reduces the risk of password breaches.

Can I use passwordless authentication with Devise?

Yes, you can use passwordless authentication with Devise. Devise is a flexible authentication solution for Rails based on Warden. It provides a number of features including passwordless authentication. You can customize Devise to send a unique token to the user’s email for authentication.

How can I implement passwordless authentication from scratch in Rails?

Implementing passwordless authentication from scratch in Rails involves creating a token model, generating unique tokens, sending these tokens to the user’s email, and authenticating the user when they click on the link containing the token. You can use Rails’ Action Mailer to send emails and SecureRandom to generate unique tokens.

What are the benefits of passwordless authentication in Rails?

Passwordless authentication in Rails offers several benefits. It eliminates the need for users to remember complex passwords, reducing the risk of password breaches. It also simplifies the authentication process, improving the user experience. Furthermore, it can be more secure than traditional password-based authentication if the email system is secure and users are educated about the importance of securing their email accounts.

Are there any drawbacks to passwordless authentication in Rails?

While passwordless authentication in Rails offers several benefits, it also has some drawbacks. The security of this method largely depends on the security of the user’s email account. If the email account is compromised, the token can be misused. Therefore, it’s crucial to ensure the email system is secure and users are educated about the importance of securing their email accounts.

How can I secure the token in passwordless authentication?

You can secure the token in passwordless authentication by using HTTPS to encrypt the communication between the server and the client. This prevents the token from being intercepted during transmission. You can also set the token to expire after a certain period of time to reduce the risk of misuse.

Can I use passwordless authentication with other authentication methods in Rails?

Yes, you can use passwordless authentication with other authentication methods in Rails. For example, you can use it with two-factor authentication to add an extra layer of security. The user would first authenticate with the token and then with a second factor, such as a fingerprint or a one-time password.

How can I test passwordless authentication in Rails?

You can test passwordless authentication in Rails by creating test cases that simulate the process of sending the token, clicking on the link, and authenticating the user. You can use Rails’ built-in testing framework or other testing libraries like RSpec and Capybara.

Can I customize the token in passwordless authentication?

Yes, you can customize the token in passwordless authentication. You can change the length of the token, the characters used in the token, and the expiration time of the token. You can use SecureRandom to generate the token and ActiveSupport::Duration to set the expiration time.

Vinoth is a Server Administrator turned Full stack web developer. He loves to try his hands on multiple programming languages but his primary programming language of choice is Ruby. He is currently a Software Engineer @ Intelllex building the server side of things. You can find more about him at avinoth.com.

authenticationGlennGRuby on Rails
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week