DEV Community

Cover image for Ruby on Rails: Native route constraint for authentication
Elvinas Predkelis
Elvinas Predkelis

Posted on • Edited on • Originally published at primevise.com

5

Ruby on Rails: Native route constraint for authentication

Since Rails 7, there's more and more tooling that enables us, developers, to roll our own authentication. Devise is great and has been an amazing companion over the years. It also has this neat little feature - an authenticated route constraint which "hides" certain routes from people that are not signed in.

If you're using devise, you might have seen something like this:

# routes.rb

authenticated { |user| user.admin? } do
  get :dashboard
end
Enter fullscreen mode Exit fullscreen mode

We'll try to replicate this natively.


Constraint

We'll leverage advanced routing constraints to make this happen. Honestly, it's not as scary as it sounds. It basically means that we'll extract the logic to a class.

Below is a barebones class for the constraint.

# app/constraints/authenticated_constraint.rb

class AuthenticatedConstraint
  def matches?(request)
    return true_if_authenticated_method
  end
end
Enter fullscreen mode Exit fullscreen mode

How does the user authentication work?

Web applications usually leverage cookies to facilitate user authentication. When a user signs in, a cookie with a unique identifier is set in the browser. This cookie is then used to identify the user on subsequent requests.

Therefore, the signing in logic in your application might look something like this:

def sign_in(user)
  Current.user = user
  cookies.encrypted.permanent[:autheticated_user_id] = user.id
end
Enter fullscreen mode Exit fullscreen mode

Similarly, when a user signs out, the cookie is removed. Pretty simple.


Accessing the cookies

In order to figure out whether a user is signed in, we'll use the same exact cookies in the constraint.

Rails usually encrypts the cookies for security reasons. Fortunately, there's decryption tooling that helps to dip our fingers into the cookie jar.

def matches?(request)
  @cookies = ActionDispatch::Cookies::CookieJar.build(request, request.cookies)

  # And then the cookies are accessible, for example:
  @cookies.encrypted[:authenticated_user_id]

  # ...
end
Enter fullscreen mode Exit fullscreen mode

Passing a block

A neat little thing that authenticated also method does is that it allows passing a block. This block helps to add additional conditions to the constraint. For example, checking whether the user is an administrator.

In other words, we'll recreate that { |user| user.admin? } part.

def initialize(&block)
  @block = block || lambda { |user| true }
end

def matches?(request)
  # ...

  # Here we could call the supplied block
  @block.call(current_user)

  # ...
end
Enter fullscreen mode Exit fullscreen mode

Voilà!

Finally, we can mix everything together and add some finishing touches to make everything more readable.

The finished constraint class is as follows:

# app/constraints/authenticated_constraint.rb

class AuthenticatedConstraint
  def initialize(&block)
    @block = block || lambda { |user| true }
  end

  def matches?(request)
    @cookies = ActionDispatch::Cookies::CookieJar.build(request, request.cookies)
    return signed_in? && @block.call(current_user)
  end

  private

  def current_user
    @user ||= User.find(@cookies.encrypted[:authenticated_user_id])
  end

  def signed_in?
    current_user.present?
  end
end
Enter fullscreen mode Exit fullscreen mode

Obviously, it might need some adjustment to fit your application.


Implementation

Adding this constraint into action is rather straight forward as authenticated method was used as an inspiration.

# routes.rb

# Routes available to admins only
constraints AuthenticatedConstraint.new{ |user| user.admin? } do
  mount Sidekiq::Web => "/sidekiq"
  get :admin_dashboard
end

# Routes available to authenticated users
constraints AuthenticatedConstraint.new do
  get :my_profile
  root to: :dashboard, as: :user_root
end
Enter fullscreen mode Exit fullscreen mode

That's it! 🎉


Wrapping up

Hopefully, this article has shed some light on how to create a route constraint for authenticated users. It's rather useful when trying to set a new root route for authenticated users. Also, limit the access to certain routes based on user roles.


If you have any questions or suggestions, feel free to reach out to me on Twitter or visit my website!

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

AWS Q Developer image

Your AI Code Assistant

Generate and update README files, create data-flow diagrams, and keep your project fully documented. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Instrument, monitor, fix: a hands-on debugging session

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️