Add Authentication to Your App With BCrypt and JWT
A good app is a secure app

In a recent software engineering interview, questions about authentication came up. It was a harsh wake-up call to realize that I am incredibly rusty in this area. I have recently had a habit of skipping authentication entirely in my projects, as they are relatively small in scale and contain dummy seed data with no sensitive information. In the past and under pressing deadlines, I had been told not to worry too much about authentication, as many companies outsource this process entirely to services specializing in cybersecurity.
With that said, these are by no means good excuses for ignoring authentication. Every software engineer needs to know the basic concepts and implementation of user authentication.
Following my interview hiccup and in the spirit of growth, I decided to dive back into this topic to create a small demo of an application with authentication using BCrypt
and JSON Web Tokens.
Building a Rails Server
To begin, we will create a Rails project with the following command:
rails new <project_name> --api --database=postgresql
Here, we are using PostgreSQL as the database and have indicated to Rails that this will be an API with the --api
flag. Without this flag, we would generate many extra unnecessary files included with the complete MVC framework.
We will install the jwt
and active_model_serializers
gems with:
bundle add jwt && bundle add active_model_serializers
Next, navigate to the Gemfile
and uncomment gem 'rack-cors'
, allowing us to establish Cross-Origin Resource Sharing (CORS) in the API. We will also uncomment gem 'bcrypt'
. Finally, run bundle install
in the terminal to install these libraries.
To enable CORS, head to config/initializers/cors.rb
and uncomment the following:
For now, change 'example.com'
to '*'
. This will allow all domains to make requests to our API. This has security implications, so for anything more than a demo, I’d recommend limiting access to only the domain of your front end.
Next, we will create the user model, controller, and serializers with the following commands:
rails g model User username password password_digest bio avatar
rails g controller api/v1/users
rails g serializer user
When generating the model, feel free to include whatever columns you like in your schema. username
, password
, and password_digest
are about the bare minimum required.
To create the database, run:
rails db:create
rails db:migrate
Great! We now have a basic API back end and can begin implementing the authentication logic.
BCrypt
In our application, we are using BCrypt
to salt users’ plaintext passwords. The salt is a bit of random data that is added to a user’s password. The salted password is then run through a hashing function. The hash function is effectively a one-way process, as it is not possible to dissect the hash to determine the original password. Thanks to BCrypt
, we now have “digested” passwords that can be safely stored in the database. Never store plaintext passwords in a database.
We could create an authenticate
method in the User model to compare users’ plaintext passwords with the password digest. It would look something like this:
However, we will use the built-in ActiveModel#has_secure_password
. We will add this to the User model with some validations to check for username uniqueness:
class User < ApplicationRecord
has_secure_password
validates :username, uniqueness: { case_sensitive: false }
end
Creating Users
We will first focus on creating new valid users. In the UsersController
, we will define a create
method as well as some strong params:
Here, we have used Rails’ built-in status code symbols, status: :created
and status: :not_acceptable
. These will throw 201 and 406 codes, respectively. We will add to our UserSerializer
the attributes we would like displayed on the client side:
class UserSerializer < ActiveModel::Serializer
attributes :username, :avatar, :bio
end
Navigate to config/routes.rb
to add the routes required by our server. We need routes to handle two actions. These are when an existing user logs in and when they send a request to view their profile. Both of these actions will require a JSON Web Token, but more on that later.
At this point, our back end is equipped to handle POST requests to the api/v1/users
endpoint. I would suggest whipping up a quick Vanilla JavaScript or React front end to test that you can successfully create new users with digested passwords. Using fetch
, the request would look something like this:
JSON Web Tokens (JWT)
JWTs provide a way to securely pass information as a JSON object. They allow us to use stateless authentication, meaning no information about the current user is stored on the server. User data is encoded with a JWT and that token is stored on the client side (the browser). It is then sent with every authenticated request for data. The graphic below sums up the process very nicely:

JWTs have a general structure of three strings separated by periods, with each string carrying encoded data. They resemble something like this:
xxxxxxxxxxx.yyyyyyyyyyyyyy.zzzzzzzzzzzzz
The first string carries the header data. This includes the type of token, jwt
, and the signing algorithm used to digitally sign and verify the payload:
{
"alg": "HS256",
"typ": "JWT"
}
The middle string includes the payload. In our case, this is the information related to the user (username, ID, etc.).
The third string represents the signature. It is created by combining the header, the encoded payload, a secret, and the algorithm specified in the header. The signature verifies that the data hasn’t changed or been tampered with during the request and can also confirm that the sender of the JWT is who they say they are:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
JWTs have two key methods: encode
and decode
. encode
takes three arguments of a payload to be encoded, an application secret of your choice, and optionally the hashing algorithm to be used. The JWT string is returned by this method. decode
takes a similar three arguments of the JWT string, an application secret, and optionally a hashing algorithm.
JWTs and the ApplicationController
As other controllers will require the ability to authenticate and authorize users, we will put this functionality at the top level in the ApplicationController
. To begin, we will create a method to encode the request payload with a token and a method to decode the token sent with subsequent requests:
When a user requests access to routes or resources protected by authorization, the user needs to send along the JWT. The token is included in the header of the fetch
request using the Bearer schema: Authorization: Bearer <token>
. In practice, this looks like:
This allows us to alter our decoded_token
method to expect the authorization header with the JWT included:
Above, we are making use of the begin/rescue
syntax, which allows us to rescue out of an exception in the event that the server is passed an invalid token. Instead of crashing, the server will return nil
.
We will also write a current_user
method that will grab the associated user whenever a token is presented for authorization. We can also define a logged_in?
method that will return a boolean depending on the return of the current_user
method:
To finish, we will define a method that will prompt the user for authorization and use before_action
to require authorization before any request to routes or resources:
Assigning tokens in the UsersController
Now that we have the functionality to create and read JWTs in the ApplicationController
, let’s call on the encode_token
in the UsersController
to automatically assign users a token when they sign up. We must also be sure to skip the authorization before creating a user. Signing up, or creating users, is the only action that does not require a user to be logged in to access:
Creating a token on login
Not only does a token need to be created on user signup, but a new token must be created every time an existing user logs in. To handle this, we will create an AuthController
:
rails g controller api/v1/auth
Inside our controller, we will write methods to create authentication for the logged-in user as well as some strong params:
Using the encode_token
method inherited from ApplicationController
, we create a token by including in the existing user’s ID in the payload. We then return this new JWT with the user’s data. The user’s data can then be stored in state on the front end (if using React or Redux) and the token can be stored on the client side.
On our front end, a fetch
request to perform the login action would include a POST method to the /api/v1/login
endpoint. As we specified in our routes, this will call the create
method in the AuthController
:
Accessing a user’s profile
Previously, we discussed how the JWT must be included in the authorization header to access protected routes and resources, such as a user’s profile. Let’s build the functionality for a logged-in user to view their profile. We will write a profile
method in the UsersController
that will send along a JSON object containing the user data, provided the user has been authorized:
And that concludes our Rails server with authentication and authorization! As I said, I find it helpful to test everything out in a basic front end. While not discussed in this article, you can find my front-end repository on my GitHub.