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 Controller
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
we’ll be using this inside the encode method
encode
def encode(payload)
JWT.encode(payload, secret_key, 'HS256')
end
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
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
login and token_authenticate methods
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)
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"])
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
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}
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
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.
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}
In the back end we had
token = request.headers["Authenticate"]
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.
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,
- On a successful log in, the backend will encode a token and find a user and send it to the front end.
- The front end stores the token into local storage
- 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.
- Logging out will clear the local storage token from the browser.
Top comments (0)