JSON Web Token Integration in Rails
In most applications you will likely have user accounts. And if you do, you certainly want to limit what a particular user has access to. JSON Web Tokens is a popular method for providing user authorization across resources inside your application on the client-side.
When you send a normal fetch request to your backend, you typically send the method
of the request (GET, POST, etc) along with a header
object. Something like:
This works great, but there isn’t enough information sent in this request to authorize the user once the request hits the backend. The additional piece of information we need is a JSON Web Token. Once a user is logged into the application, all subsequent requests will be sent along with the unique token, which provides the user access to any routes and resources that are permitted with that token.
The token should be sent as another key/value pair of the headers
object. The key
will be “Authorization” and the value
will be “Bearer token
", such as:
This request will hit the appropriate controller in your Rails backend. In this controller you will have several methods which represent actions
for your routes
. A common way to authorize access to these methods/actions is to use the before_action
macro that Rails provides at the top of your controller class.
Above, we are executing the require_login
method before running any of the action in theConversationsController
. Where does require_login
and authorize
come from? Well you can see from above that ConversationsController
inherits from ApplicationController
. So ApplicationController
is where those two methods live. And since ApplicationController
inherits from ActionController
, any method in ApplicationController
will be accessible inside all of our other controllers.
Ultimately, our request is going to the index
action inside ConversationsController
:
Below is what ApplicationController
looks like. We will go over all the code here to see what is happening as our request unfolds.
So, recall the require_login
and authorize
methods are executed first as we invoke it with the before_action
macro. The require_login
method will render a JSON error object if the user is NOT logged in. The authorize
method will render a JSON error object if the logged in user is NOT the user that has the user ID passed in the URL. This means the index
action will not actually execute if any of these error objects exist, so authorization is working. But how?
How do we know if a user is even logged in? We can see ApplicationController has a logged_in?
method which returns the double bang operator for the current_user
method. So a user is considered logged in if the logged_in?
method returns true, which means the current_user
method would have to return an actual object (user) in order for the double bang operator to yield true
.
In order for the current_user
method to return an object (user), the decode_token
method has to return true. The decode_token
method is where the magic actually happens. The token that we sent in our fetch request with “Authorization” headers
is decoded here with “JWT.decode” with our token passed in. From the decoded result, we parse out the user id inside the current_user
method. We then use the find_by
Active Record method to locate a user with that user id. From there, we are able to not only check if a user is logged in, but ensure that a user is authorized to view that content.
What’s great about the before_action
Rails macro is that we can restrict any of our actions/controllers with a single line of code. I can utilize the require_login method in my UsersController
just as easily:
A question you may have is where does token
actually come from on the frontend when sending the request? The option I used is localStorage
, which may or may not be the best way to go. You can store the token in localStorage
with the following line in your login route success response: localStorage.setItem(‘token’, jwt)
That’s it!