Log in with JWT Authentication in Rails and React

ronakvsoni

ronakvsoni

Posted on January 4, 2021

Log in with JWT Authentication in Rails and React

Alt Text

What is JWT?

JWT stands for JSON Web Token.

Why JWT?

JWT defines a compact and self-contained way for securely transmitting information between parties as a JSON object. An example of what you can use it for is Authorization.
Today I’m going to go through how to set up a login using JWT with a Ruby on Rails back end and a React front end.

The back end

Application ControllerAlt Text

We’ll need 3 methods here. A secret_key, encode, and decode method.

secret_key

def secret_key
    "anything" #a string of your choosing
end
Enter fullscreen mode Exit fullscreen mode

we’ll be using this inside the encode method

encode

def encode(payload)
    JWT.encode(payload, secret_key, 'HS256')
end
Enter fullscreen mode Exit fullscreen mode

In the encode method, we are passing in a payload. We then encode the payload, the secret key, and we are using the ‘HS256’ algorithm.

decode

def decode(token)
   JWT.decode(token, "anything", true, {algorithm: 'HS256'})[0]
end
Enter fullscreen mode Exit fullscreen mode

The decode method takes in a token. Note that the secret key here is actually the string you used, and NOT the secret_key method. JWT.decode will return an array which is why we have the [0] at the end.

The Login

Alt Text
login and token_authenticate methods

Alt Text
Routes. Note the post and get requests.

When the user logs in from the front end, we find the user by what ever param you‘re checking for.

What to take note of here is the lines:

payload = {user_id: user.id}
token = encode(payload)
Enter fullscreen mode Exit fullscreen mode

We want our payload to be unique to that user. No user should ever have the same id so it's a safe bet that the payload that will be encoded will be unique.
The token is the result of encoding the payload. We will be sending the user object and this encoded token to the front end.

token_authenticate

The way this will work might make more sense when we get to the front end. But essentially what is happening is when the user refreshes the page, normally they would be logged out. Since we are using JWT we can “stay logged in” on reload of the page.

In the login method, we sent the token to the front end. That token is stored in the browser’s local storage. When the page is refreshed, the front end sends the token from local storage and tries to find the user based on the token that was stored.

token = request.header["Authenticate"]
user = User.find(decode(token)["user_id"])
Enter fullscreen mode Exit fullscreen mode

The token is sent to the back end through headers. (We’ll see this on the front end section). Then we find the user by decoding the token.

Front End

Alt Text
On the front end, when the user first logs in, we send a fetch request to the back end.

If we look back at the login method on the backend, we sent back an object of {token: token, user: user}

So when we get our response, we need to take our token that we received and store it in local storage. To do this we write:

localStorage.setItem("token", data.token)
// remember data is {token: token, user: user}

Enter fullscreen mode Exit fullscreen mode

We also set the user to data.user. In this example code, I’m using Recoil.js. But you could use the useState hook, this.state in a class component, or writing to the Redux store.

the user can log in, receive their token and store it in local storage. If they refresh the page they will still have to log in. That's not what we wanted!

Authenticating the token

Alt Text
So here I have this useEffect hook acting as a componentDidMount lifecycle method living in my App.js component. If the page is refreshed, it will check the local storage for the token. If a token exists, it will send a get request to /login.

get "/login", to: "users#token_authenticate"
#i put my method in the users controller.
Enter fullscreen mode Exit fullscreen mode

But it’s a get request. How do we send data to the backend through a get request?
If you notice, we sent the fetch request with headers.

headers: {"Authenticate": localStorage.token}
Enter fullscreen mode Exit fullscreen mode

In the back end we had

token = request.headers["Authenticate"]
Enter fullscreen mode Exit fullscreen mode

We passed the token through the headers! Pretty Nifty.

So now, the backend checks to find a user based on that decoded token and sends that user object back to the frontend.

The user now essentially stays logged in even if the page refreshes. But also at this point, if the user logs out, he’s still logged in! We’re almost there.

The Logout

Right now the token is still stored in local storage. All we have to do is clear the token when the user logs out.
Alt Text
I have setUser({}) because I am using Routes with some conditional rendering. If the user object is empty, it routes the app to a log in page.

Summary

So the flow of what just happened is,

  1. On a successful log in, the backend will encode a token and find a user and send it to the front end.
  2. The front end stores the token into local storage
  3. If the the page is reloaded, the app will send a request to the back end to authenticate the token stored in local storage. If it is authenticated, it will send back the user object back to the front end.
  4. Logging out will clear the local storage token from the browser.
💖 💪 🙅 🚩
ronakvsoni
ronakvsoni

Posted on January 4, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related