DEV Community

Seamoon Pandey
Seamoon Pandey

Posted on

Reset password mailer implementation in rails 7 api, devise_token_auth, and sendgrid-ruby.

In this guide, we’ll walk through setting up password reset functionality in a Rails app using devise_token_auth for authentication and SendGrid for email delivery. With this, users can request password reset links sent directly to their emails.

Dependencies

Add devise_token_auth and sendgrid-ruby to your Gemfile:

# Sendgrid for sending emails
gem 'sendgrid-ruby'

# Devise for authentication
gem 'devise_token_auth'
Enter fullscreen mode Exit fullscreen mode

Then install the dependencies:

bundle install
Enter fullscreen mode Exit fullscreen mode

Or add each gem individually:

bundle add 'sendgrid-ruby'
bundle add 'devise_token_auth'
Enter fullscreen mode Exit fullscreen mode

Setup

To generate Devise token auth files, run:

rails g devise_token_auth:install User auth
Enter fullscreen mode Exit fullscreen mode

Now, open the generated migration file ...devise_token_auth_create_users.rb, and uncomment these lines:

t.string   :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string   :unconfirmed_email
Enter fullscreen mode Exit fullscreen mode

Run the migration:

bin/rails db:migrate
Enter fullscreen mode Exit fullscreen mode

In your user.rb model file, add:

extend Devise::Models
include DeviseTokenAuth::Concerns::User

# Add :recoverable for password recovery
devise :database_authenticatable, :registerable, :recoverable
Enter fullscreen mode Exit fullscreen mode

In config/initializers/devise_token_auth.rb, configure password reset URL:

config.change_headers_on_each_request = false
config.default_password_reset_url = "#{ENV['FRONTEND_URL']}/reset-password"
Enter fullscreen mode Exit fullscreen mode

Routes Configuration

In config/routes.rb, add:

mount_devise_token_auth_for "User", at: "auth", controllers: {
  passwords: "user/passwords"
}
Enter fullscreen mode Exit fullscreen mode

Controller Setup

Create controllers/user/passwords_controller.rb for handling password reset actions:

# frozen_string_literal: true
class User::PasswordsController < DeviseTokenAuth::PasswordsController
  # POST /auth/password
  def create
    email = resource_params[:email]
    if email.blank?
      return render json: { success: false, errors: ["Email cannot be blank"] }, status: :unprocessable_entity
    end

    resource_class.send_reset_password_instructions(email: email)
    render json: { success: true, message: "If the email exists, password reset instructions have been sent." }, status: :ok
  end

  # PUT /auth/password
  def update
    if password_update_params[:reset_password_token].blank?
      authenticate_user!

      unless current_user.valid_password?(password_update_params[:old_password])
        return render json: { success: false, errors: ["Old password is incorrect"] }, status: :unprocessable_entity
      end

      if current_user.update(password: password_update_params[:password])
        render json: { success: true, message: "Password updated successfully for authenticated user" }
      else
        render json: { success: false, errors: current_user.errors.full_messages }, status: :unprocessable_entity
      end

    else
      resource = resource_class.reset_password_by_token(password_update_params)
      if resource.nil?
        return render json: { success: true, message: "If the email exists, password reset instructions have been sent." }
      end

      if resource.errors.present?
        return render json: { success: false, errors: resource.errors.full_messages }, status: :unprocessable_entity
      end

      resource.update(password: password_update_params[:password])
      render json: { success: true, message: "Password updated successfully" }
    end
  rescue StandardError => e
    render json: { success: false, errors: ["An unexpected error occurred: #{e.message}"] }, status: :internal_server_error
  end

  private

  def password_update_params
    params.permit(:reset_password_token, :password, :password_confirmation, :old_password)
  end

  def resource_params
    params.permit(:email)
  end
end
Enter fullscreen mode Exit fullscreen mode

Now, users can initiate a password reset by sending a POST request to /auth/password.

Customizing the Email Template

To modify the reset email, update views/devise/mailer/reset_password_instructions.html.erb:

<p>Hello <%= @resource.email %>!</p>
<p>Someone requested a password reset. You can reset it via the link below.</p>
<% reset_url = DeviseTokenAuth.default_password_reset_url + "?reset_password_token=#{@token}" %>
<p><%= link_to 'Change my password', reset_url %></p>
<p>If you didn’t request this, please ignore this email.</p>
<p>Your password won’t change until you create a new one.</p>
Enter fullscreen mode Exit fullscreen mode

Environment Setup

  1. Sign in to SendGrid, set up your API key, and verify your sender email.
  2. Use dotenv or Rails credentials to securely store the following in your .env file:
SENDGRID_API_KEY=SG.******************
SENDER_EMAIL=your_verified_email@domain.com
Enter fullscreen mode Exit fullscreen mode
  1. Configure SendGrid in your environment files (config/environments/development.rb and config/environments/production.rb):
config.action_mailer.perform_deliveries = true
config.action_mailer.smtp_settings = {
  address: "smtp.sendgrid.net",
  port: 587,
  authentication: :plain,
  user_name: "apikey", # Keep as "apikey"
  password: ENV["SENDGRID_API_KEY"],
  domain: ENV["BASE_URL"],
  enable_starttls_auto: true
}
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

And that’s it! Now, users can reset their passwords by requesting a link through your Rails API with Devise Token Auth and SendGrid.

References

Top comments (0)