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
Setup very basic authentication with Devise in Rails 7
Kevin Luo ・ May 15 '23
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
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
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
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.
If you refuse to enter them, it will show HTTP Basic: Access denied.
Only when you enter the correct login credential, you can enter the 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:
- The client (browser) makes a request to a restricted URL
- The server return with
401 Unauthorized
with headerWWW-Authenticate: Basic
- When the browser gets this specific response, it will pop up a prompt window to let the user enter the login credential
- 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. - Browser will cache the credential and add it in the header for the later requests
(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.
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.
a2V2aW46MTIzNDU=
is the credential we just entered. It's not encrypted but just encoded in base64:
require 'base64'
Base64.decode64('a2V2aW46MTIzNDU=')
# => "kevin:12345"
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
then add login credentials in the file:
login:
username: kevin
password: 12345
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
)
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
- Yes, you can extend this mechanism to make it use different login credentials stored in the database, but in that case, maybe using
- 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)
Good article, my friend.