Search code examples
ruby-on-railsjwtdevisedevise-jwt

How to Properly Implement API Authentication with Devise in Rails?


I'm building a Rails API and am trying to implement authentication using Devise. My goal is to use JWT tokens for authentication, but I'm encountering issues with the sign-in process and generating/responding with tokens.

I've overridden the Devise::SessionsController to handle JSON requests and respond with JWT tokens upon successful authentication. However, I'm facing a NoMethodError with users_url during the sign-in process, likely due to Devise attempting to redirect, which is not applicable for API responses.

NoMethodError at /api/v1/users/sign_in
======================================

undefined method `users_url' for #<Api::V1::SessionsController:0x000000000587f0>

> To access an interactive console with this error, point your browser to: /__better_errors

Here's how I've currently set up my Api::V1::SessionsController:

class Api::V1::SessionsController < Devise::SessionsController
  respond_to :json
  skip_before_action :verify_signed_out_user, only: :destroy

  def create
    self.resource = warden.authenticate!(auth_options)
    if resource
      token = generate_jwt_token(resource)
      render json: { user: resource.as_json, token: token }, status: :created
    else
      render json: { error: 'Invalid email or password' }, status: :unauthorized
    end
  end

  private

  def generate_jwt_token(user)
    # :) 
  end
end

devise.rb

# frozen_string_literal: true

Devise.setup do |config|
  config.mailer_sender = '[email protected]'

  require 'devise/orm/mongoid'

  config.case_insensitive_keys = [:email]

  config.strip_whitespace_keys = [:email]

  config.skip_session_storage = [:http_auth, :params_auth]

  config.stretches = Rails.env.test? ? 1 : 12

  config.reconfirmable = true

  config.expire_all_remember_me_on_sign_out = true

  config.password_length = 8..128

  config.email_regexp = /\A[^@\s]+@[^@\s]+\z/

  config.reset_password_within = 6.hours

  config.sign_out_via = :delete

  config.responder.error_status = :unprocessable_entity
  config.responder.redirect_status = :see_other

  config.navigational_formats = ['*/*', :html, :json]

  config.jwt do |jwt|
    jwt.secret = Rails.application.credentials.devise_jwt_secret_key
    jwt.expiration_time = 1.day.to_i
  end

end

Questions:

How can I properly implement the sign-in process in Api::V1::SessionsController to avoid redirection attempts by Devise and ensure it's suitable for API use? What's the best practice for generating and handling JWT tokens with Devise for Rails API authentication? How should I structure the JSON response for sign-in and sign-out actions to make it more consumable by the front end?

Any advice on correctly setting up Devise for API authentication or pointing out what I might be missing would be greatly appreciated!


Solution

  • I would strongly advise against building your own auth system.

    Since you are already using Devise, I would suggest using https://github.com/waiting-for-dev/devise-jwt

    devise-jwt is a Devise extension which uses JWT tokens for user authentication. It follows secure by default principle.