DEV Community

Kevin Luo
Kevin Luo

Posted on • Updated on

Setup a basic authentication in Rails with HTTP Authentication

Introduction

When speaking of authentication, devise is usually the go-to gem developers choose in the Rails world. However, do you feel that devise is sometimes a bit overkilling?

For example, you're building a personal hobby website that you use to upload your cat's photos. It doesn't need to let users sign up, sign in, reset passwords, etc. Why do you need to use devise to create a users table to store usernames and passwords and introduce many related controllers and views? You just want a simple way to have some restricted areas.

Fortunately, Rails has a built-in method called http_basic_authenticate_with to perform a basic authentication by utilizing the HTTP Authentication framework.

If you're interested in how to use devise, I wrote an article to set up a basic environment for devise for you to play around

An example

In this example, we're going to make an application that has 2 pages, index and info. The index page will be public and anyone can access it. On the other hand, the info page will be a private page for some reason and you'll need to enter credentials to view that page.

First, let us create a new rails project:

$ rails new http_basic_auth
$ rails generate controller pages index info
Enter fullscreen mode Exit fullscreen mode

rails generate controller pages index info is a shortcut to make the controller and its actions. It will generate PagesController with 2 actions: :index and :info and the corresponding routes will be added into config/routes.rb

Second, we add http_basic_authenticate_with with credentials to the controller so the code should look like below.

class PagesController < ApplicationController
  http_basic_authenticate_with name: "kevin", password: "12345", only: :info

  def index
  end

  def info
  end
end
Enter fullscreen mode Exit fullscreen mode

That means we want to use

  • username: 'kevin'
  • password: '12345'

as the login credential. We add only: :info because we only want it to be required for the :info action.

Result

We can view the index page when enter http://localhost:3000/pages/index

Index page

However, when we want to visit http://localhost:3000/pages/info a prompt window will pop up and ask us to enter the username and the password.

login prompt

If you refuse to enter them, it will show HTTP Basic: Access denied.

HTTP Basic: Access denied.

Only when you enter the correct login credential, you can enter the info page.

Info page

That's it! Now the visitors must use 'kevin' and '12345' to enter the page.

How does it work?

So, how does it work? Is it safe?

In fact, it's an authentication feature supported by most browsers. It's called HTTP Authentication and it's defined in RFC7235. The workflow is like this:

  1. The client (browser) makes a request to a restricted URL
  2. The server return with 401 Unauthorized with header WWW-Authenticate: Basic
  3. When the browser gets this specific response, it will pop up a prompt window to let the user enter the login credential
  4. After entering the credential, the browser will send the same request again but with a header Authorization: Basic a_base64_string. a_base64_string here will be the credential encoded as base64.
  5. Browser will cache the credential and add it in the header for the later requests

http authenticate
(the diagram is from https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication)

In our case, after we enter http://localhost:3000/pages/info in the browser, you'll find that it responds with status 401 and with WWW-Authenticate: Basic realm="Application". The realm is an optional description that we can ignore now. The browser will pop up a prompt to ask you to enter login information.

prompt window for login

After you enter the correct username and password, your browser will make the same request but with the header Authorization: Basic a2V2aW46MTIzNDU=. This time, you can see the info page.

Authorization header

a2V2aW46MTIzNDU= is the credential we just entered. It's not encrypted but just encoded in base64:

require 'base64'

Base64.decode64('a2V2aW46MTIzNDU=')
# => "kevin:12345"
Enter fullscreen mode Exit fullscreen mode

I guess you may be thinking: that's too insecure! Well, you're right. Therefore, you must use HTTPS when using this authentication strategy; otherwise, the credentials can be easily stolen.

Good practice

If you feel unsafe when exposing the credential in the PagesController, we can manage them in Rail's credentials. Go to the terminal and type

EDITOR=vim rails credentials:edit
Enter fullscreen mode Exit fullscreen mode

then add login credentials in the file:

login:
  username: kevin
  password: 12345
Enter fullscreen mode Exit fullscreen mode

then replace the code of http_basic_authenticate_with in the PagesController:

http_basic_authenticate_with(
    name: Rails.application.credentials.dig(:login, :username),
    password: Rails.application.credentials.dig(:login, :password),
    only: :info
  )
Enter fullscreen mode Exit fullscreen mode

Pros and cons

Pros

  • Easy to implement
  • You don't need to change anything in the database
  • You don't even need to implement a sign-in page

Cons

  • Every request will carry the username and password you enter. It increases the risk of information leaks.
  • You can only use one pair of username and passwords for one controller.
    • Yes, you can extend this mechanism to make it use different login credentials stored in the database, but in that case, maybe using devise would be a better choice
  • You cannot customize the login prompt of browsers or the workflow of the authentication.

Conclusion

HTTP Authentication is very easy to implement in Rails. However, It has reasons for it becoming less and less popular. It's not flexible and increases the risk of leaking credentials. If your application is suitable for this method, congratulation, you can try that and save some time.

References

https://api.rubyonrails.org/v7.0.4.2/classes/ActionController/HttpAuthentication/Basic.html
https://datatracker.ietf.org/doc/html/rfc7235
https://caniuse.com/mdn-http_headers_authorization_basic

Top comments (1)

Collapse
 
stephanep profile image
Stephane Paul

Good article, my friend.