A Token of Persistence: JWT Tokens and Redux Persist
indiejesus2
Posted on August 18, 2021
A couple weeks ago, I implemented a log-in feature on my counting calories application, Counting Cookies. It was a really simplified method but it was necessary to give users private access to their records. What I was really seeking to do was persist my state in order to allow other features to work properly such as updating profile information or a single component for my daily records form.
There seems to be more than a few ways to accomplish this, all with their own strengths and drawbacks. Originally I chose a path I was most familiar with and which I described before, assigning the user’s id to session, which eventually seemed a little too simple and not compatible with keeping a user logged in. I saw a decent amount of developers choose to use tokens, but that are stored in localStorage or sessionStorage, a method that works but can be a security risk.
I chose to use JWT or JSON web tokens that are issued when a user logs in and then stored in cookies. The set-up wasn’t terribly hard, just a few updates to the backend including authenticating the token and checking if there is a current user based on the token. I also added some additional routes for logging in, logging out, and checking for a current user, to help persist state between components.
A token is considered a credential, which must be sent along when making a fetch request to the backend. Sending the credential worked out fine, but if the configuration isn’t set up correctly CORS will throw a hissy fit and prevent the application from working. CORS was originally set up to handle any requests no matter the route by using a *
or a wildcard. This is not allowed when sending highly private credentials between the front and backend, so a specific address must be set, essentially giving permission to that particular website to access the data.
Make sure the host location is correct or else you’ll waste hours searching online for a solution for a problem that doesn’t exist. In my case, I was setting my origin to my backend web address rather than the frontend where the request is originally being made. But the errors CORS give you will not give you that hint, instead you will see errors like “Access-Control-Allow-Origin missing“ and other problems with the pre-flight response. I learned about them, but not necessary to this particular problem.
Once I correctly set the origin, the credentials were being sent back and forth, but refreshing my page would still reroute to the root page which is my login page. I had to take advantage of the new current_user
validation that I had set up in my application controller in my backend. I created an action, also called current_user, that will fire when the initial component is first mounted, sending a fetch request to check if the user is indeed logged_in?
def jwt_key
ENV['SESSION_SECRET']
end
def issue_token(user)
JWT.encode({user_id: user.id}, jwt_key, 'HS256')
end
def decoded_token
begin
JWT.decode(token, jwt_key, true, { :algorithm => 'HS256' })
rescue JWT::DecodeError
[{error: "Invalid Token"}]
end
end
def authorized
render json: { message: 'Please log in' }, status: :unauthorized unless logged_in?
end
def token
request.headers['Authorization']
end
def user_id
decoded_token.first['user_id']
end
def current_user
@user ||= User.find_by(id: user_id)
end
def logged_in?
!!current_user
end
Now when I refreshed the page or tried a broken link, it would redirect to the users homepage since they were technically still signed in. That was great, until I realized my signout action wasn’t working. It’s pretty clear how to initialize a token for a user, but the drawback is the token would remain until it expired in the backend. So trying to make a fetch request to destroy the current_user was coming up fruitless, it seemed the best solution was to remove the token from the Cookies in the frontend, no fetching required.
case 'LOGOUT_USER':
// localStorage.clear();
Cookies.remove('eduResourceSession')
storage.removeItem('persist:key')
return {user: [],
loggedIn: false,
token: ""
}
Finally, I was able to log-out of my application without it redirecting to the user’s homepage, but the state wasn’t persisting on other pages. When I clicked the link for a user’s homepage, the address bar would briefly show the correct link before redirecting itself to the root page. I needed something to persist the state as it was disappearing when a separate page was called.
Wouldn’t you know it, a kind-hearted person made a package for Redux for just that purpose, Redux-Persist. It meant I had to configure a store that would be passed to the index page when initializing the provider when originally rendered.
import {createStore, applyMiddleware} from 'redux'
import { persistReducer, persistStore } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import thunkMiddleware from 'redux-thunk';
import usersReducer from './reducers/usersReducer'
import recordsReducer from './reducers/recordsReducer'
import loginReducer from './reducers/loginReducer'
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
usersReducer,
recordsReducer,
loginReducer
//sessionReducer
});
const persistenceConfigs = {
key: "loggedIn",
storage
}
const persistedReducer = persistReducer(persistenceConfigs, rootReducer)
const store = createStore(persistedReducer, applyMiddleware(thunkMiddleware));
const persistor = persistStore(store)
export { persistor, store }
It worked a little too well for my needs because now I wasn’t able to logout again, before I could try my profile link. I fixed that by adding the signoutUser
action as a click function to the link. Next I tackled the profile page, by moving it higher in the Switch statement, which finally got it to start working properly.
At last, my project felt like an actual functional website that people can use. Now I can focus more on the cosmetic look and deploying it to Heroku so it can be reviewed by Chingu before our project starts. I’m sure I’ll tell you all about it next week. Stay tuned!
Posted on August 18, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.