Handle Password and Email Changes in Your Rails API

Share this article

Handle Password and Email Changes in Your Rails API

This is part two of our series about authentication from scratch using JWT. The first part of the series can be found here. In the previous tutorial, we saw a quick overview about JWT, different authentication mechanisms, and a set of basic authentication APIs, like registration, confirmation, and login. In this part, we will see the next set of APIs such as password (reset and change) and email update.

This series is more than a JWT tutorial. Its main goal is to see how to build your own custom authentication solution from scratch and JWT is just the method we opted to use.

We will be continue building on our sample application that we developed in the first part which could be found here. If you wish to follow along, you can check out the part-i branch on the linked repository.

Password

The API we cover here is a forgot password sequence. The flow generates a password reset token along with an endpoint for the user to validate the token. This endpoint is called when a user clicks the password reset link sent to them via email. That last endpoint is for finally changing the password.

Forgot Password

The forgot password endpoint generates a password reset token, saves it in the database, and sends an email to the user. This is similar to the confirmation instructions module we saw in the first part. Let’s begin by adding the columns necessary for the password reset functionality. Run,

rails g migration AddPasswordResetColumnsToUser

And in the generated migration file, add the following:

add_column :users, :reset_password_token, :string
add_column :users, :reset_password_sent_at, :datetime

These two columns are sufficient for the purpose. reset_password_token will store the token that we generate and reset_password_sent_at tracks the time the token is sent for expiry purposes. Let’s add the endpoints now. Start by generating the password controller:

rails g controller passwords

Add the following routes to your config/routes.rb file:

post 'password/forgot', to: 'password#forgot'
post 'password/reset', to: 'password#reset'

Now, let’s add the corresponding action for the above mentioned routes to 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
 ...

Let’s go over this quickly. In the forgot action, get the email in the post request and fetch the user. If the user is found and confirmed, call the generate_password_token on the user model and send the email. The email sending part is skipped, but make sure to include the password_reset_token of the user in the email. In the reset action, get the token sent in the request and validate it via password_token_valid? and reset the password via reset_password. These methods are yet to be added to the user model, let’s do it now:

Add the below methods to 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
...

In the generate_password_token! method we generate a token using the generate_token method and store it in the reset_password_token column, alos setting the reset_password_sent_at to the current time. In the password_token_valid? method, verify the token is sent within the last 4 hours which is the reset password expiry. You are free to change it however you see fit. the reset_password! method updates new password of the user and nullifies the reset token.

Reset password set is done. You can test it by sending a post request /passwords/forgot with the email in the body and /passwords/reset with a new password and token in the body. Let’s add the password update link now.

Update Password

To add the update password, add the route to your routes file:

put 'password/update', to: 'password#update'

Here is the corresponding action in 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

The password update action is quite straightforward. Get the password from the parameter and save it to DB using the reset_password method that we declared before in the user model. You can now test the password update URL by sending a PUT request to /password/update with the new password in the body. Let’s move on to the next big functionality, Email Update.

Email Update

Email update allows a user to update their primary email on their account. Upon request, we should check if the email is already being used by any other user. If the email is OK, store it and send a verification mail to the new email. Upon confirmation, we’ll replace the primary email with the new email and clear out the token.

So, there are two APIs in total: One to make an email update request, one to actually update the email. Let’s get started.

Begin by doing a migration to add the necessary column to support this module. Generate a migration:

rails g migration AddUnconfirmedEmailTouser

Add the following content to it and run rake db:migrate:

add_column :users, :unconfirmed_email, :string

Update

Now, let’s update the routes for these two endpoints. Add these lines to config/routes.rb:

...
resources :users, only: [:create, :update] do
    collection do
        post 'email_update'
...

Add the corresponding actions to 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

Also add a before_action to do the validations on the new email, add this at top of the user controller class with the methods marked 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
    ...

Here we check if the requested email is already in use, and if the email is the same of what the account already has. If everything is fine, call update_new_email! and send the email. Note that, the email has to be sent to user’s unconfirmed_email instead of their primary one. We have used a couple of new model methods here, so let’s go define them. In models/user.rb add the below functions:

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

Here, in email_used?, apart from checking if the email is used primarily on any accounts we also check if it’s being updated and waiting for confirmation. This can be removed depending upon your needs. The confirmation_token_valid? method was added in the first part of this tutorial.

You can now test this route by sending a POST request to /users/update with email in the request body.

Email Update

Now, let’s add the action for the email update endpoint. Add this code to 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

This action is quite straightforward. Fetch the user by the token and see if the token is valid. If so, update the email and respond. Let’s add the update_new_email! method to the user model:

def update_new_email!
  self.email = self.unconfirmed_email
  self.unconfirmed_email = nil
  self.mark_as_confirmed!
end

Here we replace the primary email with the updated email and set the updated email field to nil. Also, call the mark_as_confirmed! which we added in the previous part of the series. This method nullifies the confirmation token and sets the confirmed at value. The email update endpoint is also up now. Try sending a POST request to /users/email_update with the email token we generated in previous section in the request body.

Conclusion

With that, we have arrived at the conclusion of our two-part tutorial on authentication from scratch for a Rails API. To recap, we have implemented Devise’s authentication, confirmation, password, and reconfirmation modules. Not too shabby.

The code used in this tutorial is available here. The code where this tutorial starts from is available in the part-i branch.

I hope this tutorial helped you in understanding the authentication and rolling out your own authentication system. Thanks for reading.

Frequently Asked Questions (FAQs) about Handling Password and Email Changes in Rails API

How Can I Securely Implement Password Resets in Rails API?

Implementing password resets in Rails API involves creating a password reset resource that generates a unique token when a user requests a password reset. This token is then sent to the user’s email. When the user clicks on the link in the email, they are directed to a page where they can reset their password. It’s crucial to ensure that this process is secure. You can do this by using secure hashing algorithms to store passwords, ensuring that the reset token is unique and expires after a certain period, and using secure connections (HTTPS) to transmit sensitive data.

How Can I Update User Emails in Rails API?

Updating user emails in Rails API involves creating an update action in the user controller that allows users to change their email. This action should verify the user’s current password before allowing the email change to prevent unauthorized changes. After the email is changed, it’s a good practice to send a confirmation email to the new address to verify its validity.

How Can I Test Email and Password Changes in Rails API?

You can test email and password changes in Rails API using Rails’ built-in testing framework or other testing libraries like RSpec. You should write tests that check whether the password and email update actions work as expected, whether the password reset token is generated and sent to the user’s email, and whether the user can reset their password using the token.

How Can I Handle Errors During Password and Email Changes in Rails API?

Handling errors during password and email changes in Rails API involves adding error handling code in your controllers and models. This code should catch any errors that occur during the update process and return appropriate error messages to the user. You can also use Rails’ built-in validation features to validate the data before it’s saved to the database.

How Can I Use Devise to Handle Password and Email Changes in Rails API?

Devise is a flexible authentication solution for Rails that provides several modules for handling password and email changes. The Recoverable module allows users to reset their password and sends reset instructions. The Confirmable module sends email confirmations during registration, when the user changes their email, or when their email is manually set by an admin.

How Can I Use Rails Console to Update Passwords?

Rails Console is a command line tool that allows you to interact with your Rails application. You can use it to update passwords by finding the user record, setting the password and password confirmation fields to the new password, and saving the record. However, this method should be used sparingly and only for administrative purposes, as it bypasses the usual security checks.

How Can I Build a Secure API with Rails?

Building a secure API with Rails involves several best practices. These include using secure connections (HTTPS), storing passwords securely using hashing algorithms, validating and sanitizing input data, handling errors properly, and implementing authentication and authorization to restrict access to certain resources.

How Can I Use Rails API to Send Emails?

Rails API provides Action Mailer, a framework for designing email services. You can use Action Mailer to send emails in response to certain actions in your application, such as when a user requests a password reset. Action Mailer allows you to design email templates and send emails using SMTP, Sendmail, or other delivery methods.

How Can I Implement Two-Factor Authentication in Rails API?

Implementing two-factor authentication in Rails API involves adding an extra layer of security to your authentication process. This can be done using gems like Devise and devise-two-factor. After the user enters their password, they are asked to enter a second factor, such as a code sent to their phone, to verify their identity.

How Can I Use Rails API to Handle User Authentication?

Rails API provides several tools and libraries for handling user authentication. These include Devise, a flexible authentication solution, and JWT (JSON Web Tokens), a standard for securely transmitting information between parties. You can use these tools to implement features like registration, login, password resets, and email confirmation.

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.

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