Software Architect and Security Officer with 20 years of experience, Web Dev has shifted from my main profession to a hobby as I rarely get to code by now and just love the craft. Check out my channel
Please keep in mind the implications of storing secrets such as access tokens (e.g. a JWT) in localStorage. Browser storage such as localStorage or sessionStorage do not provide sufficient isolation against e.g. XSS attacks and the secrets kept in localStorage can easily be exposed by malicious code.
Thank you for raising this important concern. You're absolutely right that storing access tokens or any sensitive information in localStorage can pose security risks, especially in the context of XSS attacks. It's crucial to consider these implications and take necessary precautions.
One alternative approach to mitigate such risks is to use techniques like HttpOnly cookies or secure authentication mechanisms. By employing secure storage methods and implementing best practices, we can enhance the overall security of our applications.
I appreciate your valuable input and the reminder to prioritize security in handling sensitive data.
Exellent article, well described and organized, easy to follow.
I didn't understand something on the router. It defines two "/" routes to main, but how it know which one use? I didn't catch how the router detects when to use the public and the authorized one.
Thank you for your kind words! I'm glad you found the article helpful.
In React Router, the routes are processed in the order they are declared. This means that the first matching route will be rendered.
When a user navigates to a specific route, React Router will check the routes in the order they are defined. If a route's path matches the current URL, React Router will render the corresponding element.
When the user is authenticated (has a token), the routesForNotAuthenticatedOnly is excluded from the route configuration. Therefore, only the routes from routesForPublic and routesForAuthenticatedOnly will be considered. If the path matches "/", React Router will render the <ProtectedRoute /> component, which leads to the "User Home Page" component being displayed.
On the other hand, when the user is not authenticated (no token), the routesForNotAuthenticatedOnly routes are included. In this case, if the path matches "/", React Router will render the "Home Page" component from routesForNotAuthenticatedOnly, because it is declared before the routes from routesForAuthenticatedOnly.
// Combine and conditionally include routes based on authentication status const router = createBrowserRouter([ ...routesForPublic, ...(!token ? routesForNotAuthenticatedOnly : []), ...routesForAuthenticatedOnly, ]);
The order of route declaration is significant. React Router processes the routes in the order they are defined, and the first matching route is rendered. In this scenario, since the path "/" matches the first route in routesForNotAuthenticatedOnly, that route's corresponding component will be rendered.
So, to clarify, when the user is not authenticated, the "Home Page" component from routesForNotAuthenticatedOnly will be rendered when the path matches "/", as it is declared before the routes from routesForAuthenticatedOnly.
By conditionally including different route configurations based on the authentication status, you can control which routes are available to the user and which components are rendered for different scenarios.
Hi, very good article, but I think it is more maintainable if we use useContext with useReducer for these cases, in this way we handle possible predefined state changes and we don't let them change in any way through the code. this way we can have a useAuthStateContext and a useAuthDispatchContext to make it more maintainable and even easier to use. greetings and congratulations!
Thank you for your feedback and congratulations! I appreciate your input and agree that using useContext with useReducer can offer better maintainability for handling state changes.
In my projects, I often utilize the combination of useContext and useReducer for managing authentication state. However, for the purpose of this article, I have chosen to simplify the implementation and use useState.
Here is the updated version of the AuthProvider component that implements the reducer pattern:
importaxiosfrom"axios";import{createContext,useContext,useMemo,useReducer}from"react";// Create the authentication contextconstAuthContext=createContext();// Define the possible actions for the authReducerconstACTIONS={setToken:"setToken",clearToken:"clearToken",};// Reducer function to handle authentication state changesconstauthReducer=(state,action)=>{switch (action.type){caseACTIONS.setToken:// Set the authentication token in axios headers and local storageaxios.defaults.headers.common["Authorization"]="Bearer "+action.payload;localStorage.setItem("token",action.payload);// Update the state with the new tokenreturn{...state,token:action.payload};caseACTIONS.clearToken:// Clear the authentication token from axios headers and local storagedeleteaxios.defaults.headers.common["Authorization"];localStorage.removeItem("token");// Update the state by removing the tokenreturn{...state,token:null};// Handle other actions (if any)default:console.error(`You passed an action.type: ${action.type} which doesn't exist`);}};// Initial state for the authentication contextconstinitialData={token:localStorage.getItem("token"),};// AuthProvider component to provide the authentication context to childrenconstAuthProvider=({children})=>{// Use reducer to manage the authentication stateconst[state,dispatch]=useReducer(authReducer,initialData);// Function to set the authentication tokenconstsetToken=(newToken)=>{// Dispatch the setToken action to update the statedispatch({type:ACTIONS.setToken,payload:newToken});};// Function to clear the authentication tokenconstclearToken=()=>{// Dispatch the clearToken action to update the statedispatch({type:ACTIONS.clearToken});};// Memoized value of the authentication contextconstcontextValue=useMemo(()=>({...state,setToken,clearToken,}),[state]);// Provide the authentication context to the children componentsreturn (<AuthContext.Providervalue={contextValue}>{children}</AuthContext.Provider>);};// Custom hook to easily access the authentication contextexportconstuseAuth=()=>{returnuseContext(AuthContext);};exportdefaultAuthProvider;
And here is the updated version of the Logout component that utilizes the clearToken function from the useAuth hook:
import{useNavigate}from"react-router-dom";import{useAuth}from"../provider/authProvider";constLogout=()=>{const{clearToken}=useAuth();constnavigate=useNavigate();// Function to handle logoutconsthandleLogout=()=>{clearToken();// Clear the authentication tokennavigate("/",{replace:true});// Navigate to the home page ("/") with replace option set to true};// Automatically logout after 3 secondssetTimeout(()=>{handleLogout();// Invoke the logout action},3*1000);return<>Logout Page</>;};exportdefaultLogout;
Again thank you for sharing your thoughts. I appreciate your support!
Good stuff Sanjay, thanks for sharing, just a little clarification about file name: authProvider.js is really authProvider.jsx
if you use .js you will get the following/similar error:
The esbuild loader for this file is currently set to "js" but it must be set to "jsx" to be able to parse JSX syntax. You can use "loader: { '.js': 'jsx' }" to do that.
Of course you have the right file name in the source code. :-)
Good post, thank you 🙏 What do you think instead of using Context API, using Effector or Zustand?
What if backend sends you an HTTP cookie (JWT), that JWT includes time of creation and time expiration. This is jus first part of the full token, with the second part stored on the server. You only need to check expiration time of the token.
Thank you for your kind words! When it comes to alternative state management solutions, libraries like Effector or Zustand can offer different approaches compared to the Context API. While I haven't personally worked with Effector and Zustand, I do plan on exploring them in the future. However, I can share that Redux Toolkit is a widely adopted and powerful state management solution that I intend to cover in an upcoming article.
Regarding JWT tokens with time of creation and expiration, it is a common practice in authentication. If the backend sends the JWT as an HTTP cookie, you can extract relevant information such as the expiration time from the token and store it on the client-side. By checking the expiration time, you can determine the token's validity. If the token has expired, you may need to handle token renewal or reauthentication based on your application's requirements.
Hi, Why do we need to create ? Why not simply do the following: const router = createBrowserRouter([ ...routesForPublic, ...(!token ? routesForNotAuthenticatedOnly : []), ...(token ? routesForAuthenticatedOnly : [] ), ]); We check the token here in createBrowerRouter itself. This is easier and much more maintainable.
For the routesForNotAuthenticatedOnly, the logic is as follows:
If the user is not authenticated (token is falsy), the routesForNotAuthenticatedOnly array will be included in the routes configuration.
If the user is authenticated (token is truthy), an empty array [] will be included instead, effectively excluding the routesForNotAuthenticatedOnly from the routes configuration.
This logic is achieved using the conditional operator (!token ? routesForNotAuthenticatedOnly : []).
For the routesForAuthenticatedOnly, the logic is as follows:
The routes are always included in the routes configuration.
However, when a user visits any of the protected routes (e.g., /, /profile, etc.), the ProtectedRoute component is responsible for checking if the user is authenticated.
If the user is not authenticated (token is falsy), they will be redirected to the /login route using the Navigate component from react-router-dom.
This redirection ensures that only authenticated users can access the protected routes.
The logic for this redirection is implemented within the ProtectedRoute component, where it checks if the user is authenticated and handles the redirection accordingly.
Overall, these mechanisms ensure that the appropriate routes are accessible based on the user's authentication status. If the user is not authenticated, they can access the routesForNotAuthenticatedOnly, and if they are authenticated, they can access the routesForAuthenticatedOnly with the added protection of the ProtectedRoute component redirecting them to the login page if needed.
Front-end developer with intermediate understanding of JavaScript, learning react. Good knowledge of UI/UX. I write about problems I face in the programming world to help people who will face similar
This article was amazing. I'm using the createBrowserRoutesFromElements function, but it seems I'll change that. A question I have for you is, how would someone handle refresh tokens using this setup? Thanks
But if we decide whether user is authenticated or not, just on the basis of existence of token, is it okay? won't if user manually add some garbage into the localStorage via inspecting, will let him sign in? Is there any solution for this? one solution I think, we can verify the validity of jwt in frontend, ig?
Your concern about relying solely on the existence of a token in localStorage for user authentication is valid. While it may not directly compromise security, it can affect the user experience. Here are two strategies to address this issue:
Token Validity and Expiration: Upon receiving a token, it's crucial to check its validity and expiration. If the token has expired, redirecting the user to the login page is appropriate. Additionally, implementing a timeout based on the token's expiration time can automatically log out the user, enhancing security.
Backend Token Validation: When redirecting the user to the home page, we can encounter an invalid token. Although the token hasn't expired, we cannot guarantee its origin. To verify that the token was indeed issued by our backend, we can create an API endpoint for token validation. This way, after checking for token expiration, we can asynchronously validate the token's authenticity with the backend, displaying a loading page to the user during this process.
// Code for Token Validity and Expiration Strategyimportaxiosfrom'axios';import{createContext,useContext,useEffect,useMemo,useState}from'react';constAuthContext=createContext();// Function to check if JWT is expiredconstcheckTokenValidity=(token)=>{if (!token)returnfalse;// Token doesn't existconstdecodedToken=JSON.parse(atob(token.split('.')[1]));constexpirationTime=decodedToken.exp*1000;// Convert to millisecondsreturnDate.now()<expirationTime;// Check if token is not expired};// Function to handle expired tokensconsthandleExpiredToken=()=>{deleteaxios.defaults.headers.common['Authorization'];localStorage.removeItem('token');// Redirect to login page or handle expired token};constAuthProvider=({children})=>{// State to hold the authentication tokenconst[token,setToken_]=useState(localStorage.getItem('token'));// Function to set the authentication tokenconstsetToken=(newToken)=>{setToken_(newToken);};// useEffect hook to handle token expiration and validity checksuseEffect(()=>{// Check if token exists and is validif (token&&checkTokenValidity(token)){axios.defaults.headers.common['Authorization']='Bearer '+token;localStorage.setItem('token',token);// Calculate the time until token expirationconstexpirationTime=JSON.parse(atob(token.split('.')[1])).exp*1000;// Convert to millisecondsconsttimeUntilExpiration=expirationTime-Date.now();// Set a timeout to automatically log out the user when the token expiressetTimeout(()=>{handleExpiredToken();},timeUntilExpiration);}else{// Token is invalid or expired, handle accordinglyhandleExpiredToken();}},[token]);// Memoized value of the authentication contextconstcontextValue=useMemo(()=>({token,setToken,}),[token]);// Provide the authentication context to the children componentsreturn<AuthContext.Providervalue={contextValue}>{children}</AuthContext.Provider>; };exportconstuseAuth=()=>{returnuseContext(AuthContext);};exportdefaultAuthProvider;
Implementing these measures not only ensures a smoother user experience but also strengthens the overall security of the authentication mechanism.
Wish I could send this to every programmer that had the same struggles that I did, not only did you gave a perfect explanation on handling the token but also routes. Great job and thank you.
While the article features JWT in the title (a clickbait apparently), there is no JTW in the content or github example, besides setToken("this is a test token");
A abordagem apresentada é adequada para aplicações simples que necessitam de autenticação básica. No entanto, para aplicações mais complexas ou que lidam com dados sensíveis, é recomendável implementar medidas adicionais de segurança, como o uso de refresh tokens, armazenamento seguro dos tokens e tratamento de expiração de sessão.
If I change const routesForNotAuthenticatedOnly = [ { path: "/", element:, }, ]; to that,this exception occurs "Objects are not valid as a React child (found: [object RegExp]). If you meant to render a collection of children, use an array instead."
how can i handle nested route. currently at / i have component
and inside i have structure of login page like in Login compo i divided page in two half one half has image and in second half based on route i render either login or sign up . so fo that i have two route set up . at / i have and at at /sign-in i have
I follow your steps, and when I login, I am navigated to the path "/" which is what I want, *but * the component/element is not showing up (it's a blank page). Only if I reload the page, it will come up. Why may that be?
I'm a front-end developer with experience in React, APIs, and the web in general. I talk about all things frontend, JavaScript, React, and how to work with APIs.
Please keep in mind the implications of storing secrets such as access tokens (e.g. a JWT) in
localStorage
. Browser storage such aslocalStorage
orsessionStorage
do not provide sufficient isolation against e.g. XSS attacks and the secrets kept inlocalStorage
can easily be exposed by malicious code.Further reading:
auth0.com/blog/secure-browser-stor...
snyk.io/blog/is-localstorage-safe-...
Thank you for raising this important concern. You're absolutely right that storing access tokens or any sensitive information in
localStorage
can pose security risks, especially in the context of XSS attacks. It's crucial to consider these implications and take necessary precautions.One alternative approach to mitigate such risks is to use techniques like HttpOnly cookies or secure authentication mechanisms. By employing secure storage methods and implementing best practices, we can enhance the overall security of our applications.
I appreciate your valuable input and the reminder to prioritize security in handling sensitive data.
Could you tell me what should change if we use http only cookie instead 👀
Exellent article, well described and organized, easy to follow.
I didn't understand something on the router. It defines two "/" routes to main, but how it know which one use?
I didn't catch how the router detects when to use the public and the authorized one.
Thank you for your kind words! I'm glad you found the article helpful.
In React Router, the routes are processed in the order they are declared. This means that the first matching route will be rendered.
When a user navigates to a specific route, React Router will check the routes in the order they are defined. If a route's path matches the current URL, React Router will render the corresponding element.
When the user is authenticated (has a token), the
routesForNotAuthenticatedOnly
is excluded from the route configuration. Therefore, only the routes fromroutesForPublic
androutesForAuthenticatedOnly
will be considered. If the path matches "/", React Router will render the<ProtectedRoute />
component, which leads to the "User Home Page" component being displayed.On the other hand, when the user is not authenticated (no token), the
routesForNotAuthenticatedOnly
routes are included. In this case, if the path matches "/", React Router will render the "Home Page" component fromroutesForNotAuthenticatedOnly
, because it is declared before the routes fromroutesForAuthenticatedOnly
.The order of route declaration is significant. React Router processes the routes in the order they are defined, and the first matching route is rendered. In this scenario, since the path "/" matches the first route in
routesForNotAuthenticatedOnly
, that route's corresponding component will be rendered.So, to clarify, when the user is not authenticated, the "Home Page" component from
routesForNotAuthenticatedOnly
will be rendered when the path matches "/", as it is declared before the routes fromroutesForAuthenticatedOnly
.By conditionally including different route configurations based on the authentication status, you can control which routes are available to the user and which components are rendered for different scenarios.
That was a Mega explanation!
All clear, thanks!
Hi, very good article, but I think it is more maintainable if we use useContext with useReducer for these cases, in this way we handle possible predefined state changes and we don't let them change in any way through the code. this way we can have a useAuthStateContext and a useAuthDispatchContext to make it more maintainable and even easier to use. greetings and congratulations!
Thank you for your feedback and congratulations! I appreciate your input and agree that using
useContext
withuseReducer
can offer better maintainability for handling state changes.In my projects, I often utilize the combination of
useContext
anduseReducer
for managing authentication state. However,for the purpose of this article, I have chosen to simplify the implementation and use
useState
.Here is the updated version of the
AuthProvider
component that implements the reducer pattern:And here is the updated version of the
Logout
component that utilizes theclearToken
function from theuseAuth
hook:Again thank you for sharing your thoughts. I appreciate your support!
this guy using A.I to reply lmao
Good stuff Sanjay, thanks for sharing,
just a little clarification about file name:
authProvider.js is really authProvider.jsx
if you use .js you will get the following/similar error:
Of course you have the right file name in the source code. :-)
Good post, thank you 🙏
What do you think instead of using Context API, using Effector or Zustand?
What if backend sends you an HTTP cookie (JWT), that JWT includes time of creation and time expiration. This is jus first part of the full token, with the second part stored on the server. You only need to check expiration time of the token.
Thank you for your kind words! When it comes to alternative state management solutions, libraries like
Effector
orZustand
can offer different approaches compared to the Context API. While I haven't personally worked with Effector and Zustand, I do plan on exploring them in the future. However, I can share thatRedux Toolkit
is a widely adopted and powerful state management solution that I intend to cover in an upcoming article.Regarding JWT tokens with time of creation and expiration, it is a common practice in authentication. If the backend sends the JWT as an HTTP cookie, you can extract relevant information such as the expiration time from the token and store it on the client-side. By checking the expiration time, you can determine the token's validity. If the token has expired, you may need to handle token renewal or reauthentication based on your application's requirements.
Hi,
Why do we need to create ? Why not simply do the following:
const router = createBrowserRouter([
...routesForPublic,
...(!token ? routesForNotAuthenticatedOnly : []),
...(token ? routesForAuthenticatedOnly : [] ),
]);
We check the token here in createBrowerRouter itself. This is easier and much more maintainable.
For the
routesForNotAuthenticatedOnly
, the logic is as follows:routesForNotAuthenticatedOnly
array will be included in the routes configuration.[]
will be included instead, effectively excluding theroutesForNotAuthenticatedOnly
from the routes configuration.(!token ? routesForNotAuthenticatedOnly : [])
.For the
routesForAuthenticatedOnly
, the logic is as follows:/
,/profile
, etc.), theProtectedRoute
component is responsible for checking if the user is authenticated./login
route using theNavigate
component fromreact-router-dom
.ProtectedRoute
component, where it checks if the user is authenticated and handles the redirection accordingly.Overall, these mechanisms ensure that the appropriate routes are accessible based on the user's authentication status. If the user is not authenticated, they can access the
routesForNotAuthenticatedOnly
, and if they are authenticated, they can access theroutesForAuthenticatedOnly
with the added protection of theProtectedRoute
component redirecting them to the login page if needed.Great Article.
Newly learned this function.
createBrowserRouter
Great Article
Great!
It described in good order and refined explanations.
Thanks for sharing what a great stuff easy to follow.
This article was amazing. I'm using the createBrowserRoutesFromElements function, but it seems I'll change that.
A question I have for you is, how would someone handle refresh tokens using this setup? Thanks
wow , awesome tutorial, straight to the point! just created an account here to give feedback :D
But if we decide whether user is authenticated or not, just on the basis of existence of token, is it okay? won't if user manually add some garbage into the localStorage via inspecting, will let him sign in?
Is there any solution for this?
one solution I think, we can verify the validity of jwt in frontend, ig?
same question
Your concern about relying solely on the existence of a token in localStorage for user authentication is valid. While it may not directly compromise security, it can affect the user experience. Here are two strategies to address this issue:
Implementing these measures not only ensures a smoother user experience but also strengthens the overall security of the authentication mechanism.
Wish I could send this to every programmer that had the same struggles that I did, not only did you gave a perfect explanation on handling the token but also routes.
Great job and thank you.
While the article features JWT in the title (a clickbait apparently), there is no JTW in the content or github example, besides
setToken("this is a test token");
A abordagem apresentada é adequada para aplicações simples que necessitam de autenticação básica. No entanto, para aplicações mais complexas ou que lidam com dados sensíveis, é recomendável implementar medidas adicionais de segurança, como o uso de refresh tokens, armazenamento seguro dos tokens e tratamento de expiração de sessão.
If I change
const routesForNotAuthenticatedOnly = [
{
path: "/",
element:,
},
];
to that,this exception occurs "Objects are not valid as a React child (found: [object RegExp]). If you meant to render a collection of children, use an array instead."
You need to provide the element you want to render when a path is visited.
const routesForNotAuthenticatedOnly = [
{
path: "/",
element: 'NEED_TO_PROVIDE_COMPONENT_HERE',
},
];
how can i handle nested route.
currently at / i have component
and inside i have structure of login page like in Login compo i divided page in two half one half has image and in second half based on route i render either login or sign up . so fo that i have two route set up . at / i have and at at /sign-in i have
with your auth set up this is not working.
Nice article although, I face a problem.
I follow your steps, and when I login, I am navigated to the path "/" which is what I want, *but * the component/element is not showing up (it's a blank page). Only if I reload the page, it will come up. Why may that be?
Exellent article
Very nice and detailed article. Simplified the concept for me.
very good template thanks a lot
Thank you so much man for this beautiful article with the simplest explanation.
Very helpful.
Great work here
Thanks Alot❤️ , Article is super simple to understand even I am beginner.
Great post!
Greate Article