Rails Authentication with BCrypt & JWT, made simple and clear
Every time the topic turns to Authentication, people seem to lose motivation. I’m going to present an easy, step-by-step authentication guide with Ruby on Rails, and the gems BCrypt and JWT. I promise this will be as clear cut as can be!
First, let’s go through the logic of BCrypt and JWT. BCrypt is a “one-way through” password hashing method, which is analogous to a meat grinder. One cannot feasibly reverse the process. It “salts” the hashing process, effectively adding additional data that generates uniqueness amongst every hash.
JWT, or JSON Web Tokens, on the other hand, is an open internet standard for authentication. These “tokens” are effectively used for credentials, and are “exchanged” for data. If one, for example, possesses a token, it can be exchanged with the backend for user data in return. Assuming Rails 5+, the gem ‘jwt’ must first be added to the gemfile. Furthermore, BCrypt must be uncommented.
The database will be seeded with two members for demonstration, as shown below with the schema included.

Thanks to BCrypt, the password_digest field is hidden when plainly viewing the database. Here are the users to seed.

When entering rails console, User.all is viewed. The password field displays [FILTERED] instead of the initialized password, thanks to BCrypt. BCrypt also allows us to run a method called authenticate on the user. After assigning the first user to the variable “first”, I call the authenticate method, first passing in “hello” as the password, which returns false. Only when I authenticate with the correct password, “hi123”, that the user data is returned.

Three custom route endpoints are made, /login which will log in a user, /create, which will create a new user, and /profile, which will display a user’s data.

After this, we’ll explore the actual methods, which are all located in the users_controller. Let’s first explore the /create method.

We’ll start the rails server, and in any REST client, like Insomnia, we make a get request to localhost:3000/create, and in the headers, we add a username, age, and password field, like so:

What we get in return is a status code 200, and a token in return. Let’s explore the actual def create method to see what’s going on. We instantiate a newUser, which is a User.new generated from the request.headers[:username], request.headers[:age], and request.headers[:password]. If the newUser is valid, we save the user, and set a payload object of user equal to the ID of the created user. Then, we create a token with JWT.encode.
The first field is the payload to deliver, the second field is the hmac_secret, which can be set to anything, but only the server should know. Mine is set to “okcool”. The third field is the encryption method, which is “HS256” here. More info can be found here: https://github.com/jwt/ruby-jwt. If the user is not valid (maybe does not satisfy validations, or is missing an age field in headers), an error json is returned.
All that’s left to do is to render the token to the frontend as JSON, which can be stored in, say, localStorage.token. Let’s now explore the /login method, which is appropriate when a user already exists.

Interesting! the login method is quite similar to the create method! Now I’ve specified here that we will be logging in by passing a username and password into the headers field. Therefore, userFound is either set to the user found, or is false. if userFound is set to a found user, we run .authenticate on it with the request.headers[:password]. If the password is correct, then we do the same as the create method, setting a payload and encrypting a JWT token and rendering it. Else, we render “Failed, wrong password.”. The error message should actually be “wrong username/password” instead.
Wrong password:

Correct password, we get a token in response:

Now that we have access to the token, we can exchange it for that specific User’s information any time, with the /profile endpoint.

Our decoded_token is set to JWT.decode with the token passed inside the header as the first parameter, our hmac_secret as the second, true as the third, and the algorithm used as the fourth.
When we find the user from the user_id in the token, we simply render the user as JSON.

Now that mostly encompasses the essence of rails authentication with JWT and BCrypt! As we see, most of it is logical steps, and much of the code body is reused in the /login and /create methods! I hope this made Authentication easier to understand.
