Setting Up JWT Authentication in a Rails API (part I)
If you want to use signup and login for one of your apps, you should get familiar with at least one of the ways to authenticate your users. This is a short overview of how to set up authentication on the server side using JSON Web Tokens (JWTs). Check out part II for the frontend setup, where I use React.
JWT authentication is great because it gives you the flexibility of authenticating users in a stateless way. That means that a user’s data is encrypted in a token in the form of a JSON object. The user’s data is never stored in a database. Instead, the token is sent along with every authentication request and decrypted in the server. So, let’s see how it all works.
Gems
After setting up your Rails API, which I will not cover here, you need to install some gems:
bundle add jwt && bundle add active_model_serializers
Next, uncomment out rack-cors
and bcrypt
in your Gemfile. Bundle install once more. Now, uncomment the code in your cors.rb file, and change origins from example.com
to *
. This is very important—otherwise, you will get a CORS error. Your file should look like this:
However, before deploying your app, you will want to change allowed origins from *
, which means “all,” to a more specified origin.
Setting Up Users
After creating your User model with a username
and password_digest
, you should add has_secure_password
, which encrypts your password using ActiveModel, and add validations too:
Then, create a user serializer:
rails g serializer user
Make sure to run rails db:create
and rails db:migrate
before continuing.
Afterwards, you can start to set up your users controller. The create
method below, along with the private user_params
, is what enables us to create a user upon signup.
You should also update your user serializer at this point, since you will call upon it in your create method above.
Routes
Next, you can update your routes. You can ignore lines 8–10.
Test Out SignUp
By this point, you should be able to successfully create/signup a new user. Try it before proceeding with the rest of this tutorial. After setting up a controlled form, here’s an example of how I did it in React (Note: We have not encoded a token yet; we’re simply testing the ability to create a user object with username and password):
If you see a new user in your console, please proceed.
Protecting Your App at the Highest Level: Application Controller
So far, we’ve set up the ability for a user to signup, but that doesn’t safeguard your app. You need to lock it down so that users don’t have access to any other part of the app unless they’re signed up, logged in, or staying logged in. Since all controllers inherit from the Application Controller, it’s best to place our “gatekeeper” functionality all the way at the top.
I’ll explain the code below.
The very first thing our app does (line 2) is call on the authorized
method (line 42), which checks if the logged_in?
method returns truthy. The logged_in?
method will check if the current_user
method executes successfully. The current_user
method will check if the decoded_token
method runs OK. If the decoded_token
method detected that the auth_header
method found an Authorization header, then decoded_token
will finish executing, which allows current_user
to finish. If any of these methods fail, the authorized
method will deny the user access.
Issuing a Token for a User
So what about the encode_token
method? That’s actually the method that we need to create a token for a user in the first place. We will use that method in our users controller, so let’s update it now. Refer to line 19 and below. Also, don’t forget to add the skip_before_action
(line 2), so our app doesn’t automatically shut down without giving our users the chance to sign in through the create method.
Notice that if our user instance is valid we create a token variable (line 22) and call on the encode_token
method and pass in the user’s id. Recall that this method takes in a “payload” for its parameters. That’s because JWTs are made up of 3 parts—3 strings, to be exact—separated by periods:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyMH0.46frU1AjZSzpt5vX-I9LR6iWQ7jYZzTB8W_TAZkm1qU
- The first part is the header.
- The second part is the payload (data like our user’s id).
- The third part is a hash of the header and the payload, known as the signature. It includes a secret key, which you should ideally hide in a .env file and then list it in gitignore.
Now, for Login
At this point, a user can signup and be issued a token. So, let’s add login functionality. First, we’ll need an auth controller.
rails g controller api/v1/auth
Then, add in this code:
We use skip_before_action
(line 2) just like we did in our users controller because we want the app to allow users to login through the create action of our auth controller. Notice that we use the encode_token
method for login too. That’s because a user needs a new token each time they fill out the login form. We’ll get to “staying logged in” in part II—how to set up the client side (during which you WON’T need a new JWT).
In the meantime, go ahead and test out what you have so far. You should be able to sign up or login a user.
Sources: