The goal of this post is to set up user authentication using a JSON web token (JWT) for a Rails API.
Normally, when I am working with rails I use a session based authentication. This means I let my server side do all the heavy lifting. With token based authentication I'm not gonna store anything on the server, but I will create a unique encoded token that will be needed anytime I'm accessing the backend. This token will be placed in the header of my requests (as a credential) and be used anytime I need to authenticate a user. To begin there are really two routes I can take. I can build my own token based authentication solution in rails (reinventing wheels), or I can see what open source solutions are available. We're gonna see what's available.
A quick Google search of "rails api JWT authentication gem" should lead us to Knock.
Seamless JWT authentication for Rails API
Knock is an authentication solution for Rails API-only application based on JSON Web Tokens.
Why should I use this?
- It's lightweight.
- It's tailored for Rails API-only application.
- It's stateless.
- It works out of the box with services like Auth0
Is this being maintained?
Yes No Yes!
Currently being maintained with the help of @andrerpbts
Slowly moving through the backlog of issues & PRs and starting to build a roadmap for the next version.
Add this line to your application's Gemfile:
$ bundle install
Knock makes one assumption about your user model:
It must have an
authenticate method, similar to the one added by has_secure_password.
class User < ActiveRecord::Base has_secure_password end
has_secure_password is recommended, but you don't have to as long as your user model implements an
Knock was built solely to provide an authentication solution for Rails APIs using JWTs, exactly what we want. Knock has a very good Readme so to begin we're just gonna follow that.
First thing we're gonna do is add the necessary gems to our gemfile. These gems are...
- gem 'knock' # Authentication Solution
- gem 'bcrypt', '~> 3.1.7' # To hash our passwords
- gem 'active_model_serializers' #Used to serialize our data (Note: As of this writing AMS is undergoing large development changes, if looking for another solution I'd recommend fast_jsonapi)
- gem 'jwt' #Ruby implementation of RFC 7519 OAuth JWT standard
- gem 'rack-cors' #this is a necessary for communication with our client
Next lets run bundle install. Now we are ready to start implementing Knock. First thing we are going to do is create a knock initializer, this will contain all the information about our configuration options in relation to knock.
$ rails generate knock:install
The initializer file will look as so in its simplest form...
Note: When generating this initializer we are given a bunch of configuration options. These options are a good idea to implement, but for the sake of this post we are gonna keep it simple.
Next thing we need to do is implement a Token Controller. This will provide a way for our users to sign in.
$ rails generate knock:token_controller user
After generating this controller we will also need to add a login route to our routes file. This would be a good time to talk about namespacing. A really good way to keep your code clean and organized is to name space it. For this project we are gonna prepend all of our routes with /api/. To accomplish this, we want to use namespace :api in our routes file as well as nest all of our controllers under an api folder. The route file at this point is shown below.
While our user token controller at controllers/api/user_token_controller.rb will look like this.
Note: For this project we are using Rails 5.2 we need to include the skip_before_action :verify_authenticity_token. If we do not include that in the UserTokenController we will get a 422 response from our server when we try to log in. I found this out looking at the issues on github. It is always a good idea to look at those before implementing.
Now the next steps are to implement the model, controller, serializer, and accompanying migration for a user. To do this I used a scaffold generator.
For this application our user will have the following data...
The model looks as follows.
What we have above are validations for the presence of all parameters, as well as a unique email, and a password of minimum length of 7. We also have the method has_secure_password. This is critical for knock to work. According to the documentation Knock makes one assumption about our user model. It must have an authenticate method, similar to has_secure_password. We are able to call this method due to our bcrypt gem. It is recommended to just use has_secure_password, but we don't have to as long as our user model implements an authenticate method with the same behavior.
We also need user routes in our route file. For now, we will have all the user routes available to us. Our routes file will now look like this.
Next we need to work on the controller. Since we used a scaffold generator a lot of our method are created. We will, however, need to make two methods. The methods are
- find #A method that we will use to find a user based on an email
- set_user #A method that will set the user.
These methods are shown below.
We will also need to add a find_user route to our routes file.
By this point we should have what we need to log in a user. So let's create a user and use Postman to log them in.
In my seeds file
Then in postman...
And what we will get back will be our token.
What we would then do is place that token in the header of our request as a credential, and every time we want to access a resource the controller in charge of that resource would authenticate the user. An example of this is shown below in a controller for one of my projects.
What I hope to do next is take the inputs we did in postman and replicate that in React. I plan on writing that post up soon! Thanks for reading and if you have any questions please let me know!