Session Got Sent and Again Went Missing React
The author selected Artistic Commons to receive a donation as part of the Write for DOnations program.
Introduction
Many spider web applications are a mix of public and individual pages. Public pages are available to anyone, while a private folio requires a user login. You can utilize authentication to manage which users have access to which pages. Your React application will need to handle situations where a user tries to access a private folio before they are logged in, and you volition need to relieve the login data once they have successfully authenticated.
In this tutorial, you'll create a React awarding using a token-based authentication system. You'll create a mock API that will return a user token, build a login folio that will fetch the token, and check for authentication without rerouting a user. If a user is not authenticated, you'll provide an opportunity for them to log in and and so allow them to continue without navigating to a dedicated login page. As you build the application, you'll explore different methods for storing tokens and will acquire the security and experience merchandise-offs for each approach. This tutorial volition focus on storing tokens in localStorage
and sessionStorage
.
Past the stop of this tutorial, you'll exist able to add authentication to a React awarding and integrate the login and token storage strategies into a consummate user workflow.
Prerequisites
-
You will need a evolution surroundings running Node.js; this tutorial was tested on Node.js version 10.22.0 and npm version 6.14.6. To install this on macOS or Ubuntu 18.04, follow the steps in How to Install Node.js and Create a Local Development Environment on macOS or the Installing Using a PPA section of How To Install Node.js on Ubuntu 18.04.
-
A React development environment set with Create React App, with the non-essential boilerplate removed. To gear up this upwardly, follow Pace one — Creating an Empty Projection of the How To Manage State on React Grade Components tutorial. This tutorial will use
auth-tutorial
every bit the projection proper name. -
You will exist fetching information from APIs using React. Yous can larn almost working with APIs in How To Telephone call Web APIs with the useEffect Claw in React.
-
You will also need a bones knowledge of JavaScript, HTML, and CSS, which y'all can observe in our How To Build a Website With HTML series, How To Style HTML with CSS, and in How To Code in JavaScript.
Step 1 — Building a Login Page
In this step, you'll create a login page for your application. You'll start past installing React Router and creating components to correspond a full application. Then you'll render the login page on any route and so that your users can login to the awarding without existence redirected to a new folio.
By the stop of this footstep, you lot'll have a basic application that will render a login page when a user is not logged into the awarding.
To begin, install react router with npm
. There are two different versions: a web version and a native version for use with React Native. Install the spider web version:
- npm install react-router-dom
The package will install and you'll receive a message when the installation is complete. Your message may vary slightly:
Output
... + react-router-dom@v.2.0 added eleven packages from 6 contributors, removed 10 packages and audited 1945 packages in 12.794s ...
Next, create ii components called Dashboard
and Preferences
to act as private pages. These will represent components that a user should not see until they have successfully logged into the awarding.
First, create the directories:
- mkdir src/components/Dashboard
- mkdir src/components/Preferences
Then open up Dashboard.js
in a text editor. This tutorial will employ nano:
- nano src/components/Dashboard/Dashboard.js
Within of Dashboard.js
, add an <h2>
tag with the content of Dashboard
:
auth-tutorial/src/components/Dashboard/Dashboard.js
import React from 'react' ; export default function Dashboard ( ) { render ( <h2>Dashboard< /h2> ) ; }
Save and close the file.
Repeat the aforementioned steps for Preferences
. Open up the component:
- nano src/components/Preferences/Preferences.js
Add the content:
auth-tutorial/src/components/Preferences/Preferences.js
import React from 'react' ; export default function Preferences ( ) { render ( <h2>Preferences< /h2> ) ; }
Save and close the file.
Now that you lot have some components, y'all demand to import the components and create routes inside of App.js
. Bank check out the tutorial How To Handle Routing in React Apps with React Router for a total introduction to routing in React applications.
To begin, open App.js
:
- nano src/components/App/App.js
Then import Dashboard
and Preferences
by calculation the following highlighted code:
auth-tutorial/src/components/App/App.js
import React from 'react' ; import './App.css' ; import Dashboard from '../Dashboard/Dashboard' ; import Preferences from '../Preferences/Preferences' ; function App ( ) { return ( < > < / > ) ; } consign default App;
Adjacent, import BrowserRouter
, Switch
, and Route
from react-router-dom
:
auth-tutorial/src/components/App/App.js
import React from 'react' ; import './App.css' ; import { BrowserRouter, Route, Switch } from 'react-router-dom' ; import Dashboard from '../Dashboard/Dashboard' ; import Preferences from '../Preferences/Preferences' ; function App ( ) { return ( < > < / > ) ; } export default App;
Add a surrounding <div>
with a className
of wrapper
and an <h1>
tag to serve as a template for the application. Be certain that yous are importing App.css
then that yous can employ the styles.
Next, create routes for the Dashboard
and Preferences
components. Add BrowserRouter
, then add a Switch
component as a child. Inside of the Switch
, add a Route
with a path
for each component:
tutorial/src/components/App/App.js
import React from 'react' ; import './App.css' ; import { BrowserRouter, Road, Switch } from 'react-router-dom' ; import Dashboard from '../Dashboard/Dashboard' ; import Preferences from '../Preferences/Preferences' ; office App ( ) { return ( <div className= "wrapper" > <h1>Application< /h1> <BrowserRouter> <Switch> <Route path= "/dashboard" > <Dashboard / > < /Route> <Route path= "/preferences" > <Preferences / > < /Route> < /Switch> < /BrowserRouter> < /div> ) ; } consign default App;
Salve and close the file.
The last step is to add some padding to the main <div>
and then your component is non directly at the edge of the browser. To practice this, you lot will change the CSS.
Open App.css
:
- nano src/components/App/App.css
Replace the contents with a class of .wrapper
with padding
of 20px
:
auth-tutorial/src/components/App/App.css
.wrapper { padding : 20px; }
Relieve and close the file. When you practice, the browser will reload and you'll observe your basic components:
Check each of the routes. If you lot visit http://localhost:3000/dashboard
, you'll find the dashboard page:
Your routes are working as expected, but in that location is a slight problem. The route /dashboard
should exist a protected folio and should not be viewable past an unauthenticated user. In that location are dissimilar ways to handle a private folio. For instance, yous can create a new route for a login folio and utilise React Router to redirect if the user is not logged in. This is a fine approach, but the user would lose their route and accept to navigate back to the folio they originally wanted to view.
A less intrusive option is to generate the login page regardless of the route. With this arroyo, y'all'll return a login folio if there is non a stored user token and when the user logs in, they'll be on the aforementioned road that they initially visited. That means if a user visits /dashboard
, they will notwithstanding be on the /dashboard
route after login.
To begin, make a new directory for the Login
component:
- mkdir src/components/Login
Next, open Login.js
in a text editor:
- nano src/components/Login/Login.js
Create a basic form with a submit <button>
and an <input>
for the username and the countersign. Be certain to set the input type for the countersign to password
:
auth-tutorial/src/components/Login/Login.js
import React from 'react' ; consign default office Login ( ) { return ( <grade> <characterization> <p>Username< /p> <input type= "text" / > < /label> <label> <p>Password< /p> <input blazon= "password" / > < /label> <div> <button type= "submit" >Submit< /push> < /div> < /class> ) }
For more than on forms in React, check out the tutorial How To Build Forms in React.
Adjacent, add an <h1>
tag asking the user to log in. Wrap the <class>
and the <h1>
in a <div>
with a className
of login-wrapper
. Finally, import Login.css
:
auth-tutorial/src/components/Login/Login.js
import React from 'react' ; import './Login.css' ; export default function Login ( ) { render ( <div className= "login-wrapper" > <h1>Delight Log In< /h1> <form> <label> <p>Username< /p> <input type= "text" / > < /label> <label> <p>Password< /p> <input blazon= "password" / > < /label> <div> <button type= "submit" >Submit< /button> < /div> < /course> < /div> ) }
Save and shut the file.
Now that you take a basic Login
component, yous'll need to add some styling. Open Login.css
:
- nano src/components/Login/Login.css
Middle the component on the page past adding a display
of flex
, and then setting the flex-direction
to cavalcade
to marshal the elements vertically and adding align-items
to middle
to make the component centered in the browser:
auth-tutorial/src/components/Login/Login.css
.login-wrapper { display : flex; flex-direction : column; align-items : eye; }
For more information on using Flexbox, see our CSS Flexbox Cheatsheet
Salvage and close the file.
Finally, you lot'll need to return it within of App.js
if in that location is no user token. Open App.js
:
- nano src/components/App/App.js
In Footstep 3, you'll explore options for storing the token. For now, you tin can store the token in memory using the useState
Hook.
Import useState
from react
, then phone call useState
and set return values to token
and setToken
:
auth-tutorial/src/components/App/App.js
import React , { useState } from 'react' ; import { BrowserRouter, Route, Switch } from 'react-router-dom' ; import './App.css' ; import Dashboard from '../Dashboard/Dashboard' ; import Preferences from '../Preferences/Preferences' ; function App ( ) { const [token, setToken] = useState ( ) ; render ( <div className= "wrapper" > <h1>Awarding< /h1> <BrowserRouter> <Switch> <Route path= "/dashboard" > <Dashboard / > < /Road> <Route path= "/preferences" > <Preferences / > < /Road> < /Switch> < /BrowserRouter> < /div> ) ; } export default App;
Import the Login
component. Add together a conditional statement to display Login
if the token
is falsy.
Pass the setToken
function to the Login
component:
auth-tutorial/src/components/App/App.js
import React, { useState } from 'react' ; import { BrowserRouter, Route, Switch } from 'react-router-dom' ; import './App.css' ; import Dashboard from '../Dashboard/Dashboard' ; import Login from '../Login/Login' ; import Preferences from '../Preferences/Preferences' ; function App ( ) { const [token, setToken] = useState ( ) ; if ( !token) { return <Login setToken= {setToken} / > } render ( <div className= "wrapper" > <h1>Application< /h1> <BrowserRouter> <Switch> <Route path= "/dashboard" > <Dashboard / > < /Route> <Route path= "/preferences" > <Preferences / > < /Route> < /Switch> < /BrowserRouter> < /div> ) ; } consign default App;
For at present, in that location is no token; in the next step, you'll telephone call an API and ready the token with the return value.
Salvage and close the file. When yous exercise, the browser will reload and you'll encounter the login page. Notice that if you visit http://localhost:3000/dashboard
, you'll nonetheless notice the login page since the token has not yet been set up:
In this step, y'all created an application with individual components and a login component that will display until you ready a token. You too configured routes to brandish the pages and added a check to display the Login
component on every route if the user is not however logged into the application.
In the next pace, you'll create a local API that volition return a user token. Yous'll call the API from the Login
component and save the token to memory on success.
Footstep ii — Creating a Token API
In this stride, you'll create a local API to fetch a user token. You'll build a mock API using Node.js that will return a user token. You'll then call that API from your login page and return the component after you successfully call up the token. By the terminate of this step, y'all'll take an awarding with a working login page and protected pages that will only be accessible after login.
You are going to need a server to act every bit a backend that will return the token. You can create a server quickly using Node.js and the Express web framework. For a detailed introduction to creating an Limited server, see the tutorial Basic Express Server in Node.js.
To start, install express
. Since the server is not a requirement of the final build, exist sure to install equally a devDependency
.
Y'all'll also need to install cors
. This library volition enable cross origin resources sharing for all routes.
Warning: Do non enable CORS for all routes in a production application. This can lead to security vulnerabilities.
- npm install --salvage-dev express cors
When the installation is complete, you lot'll receive a success message:
Output
... + cors@2.8.five + express@4.17.i removed 10 packages, updated 2 packages and audited 2059 packages in 12.597s ...
Next, open a new file called server.js
in the root of your application. Practice not add this file to the /src
directory since you do non want it to be function of the final build.
- nano server.js
Import limited
, then initialize a new app past calling limited()
and saving the result to a variable called app
:
auth-tutorial/server.js
const express = crave ( 'express' ) ; const app = express ( ) ;
After creating the app
, add cors
as a middleware. Starting time, import cors
, then add it to the application by calling the use
method on app
:
auth-tutorial/server.js
const express = require ( 'express' ) ; const cors = crave ( 'cors' ) ; const app = express ( ) ; app. utilise ( cors ( ) ) ;
Next, listen to a specific road with app.use
. The first argument is the path the application volition listen to and the second statement is a callback function that volition run when the awarding serves the path. The callback takes a req
statement, which contains the request data and a res
statement that handles the consequence.
Add together in a handler for the /login
path. Call res.transport
with a JavaScript object containing a token:
auth-tutorial/server.js
const limited = require ( 'express' ) ; const cors = crave ( 'cors' ) const app = express ( ) ; app. employ ( cors ( ) ) ; app. use ( '/login' , ( req, res ) => { res. send ( { token : 'test123' } ) ; } ) ;
Finally, run the server on port 8080
using app.heed
:
auth-tutorial/server.js
const express = require ( 'express' ) ; const cors = crave ( 'cors' ) const app = limited ( ) ; app. use ( cors ( ) ) ; app. employ ( '/login' , ( req, res ) => { res. send ( { token : 'test123' } ) ; } ) ; app. listen ( 8080 , ( ) => console. log ( 'API is running on http://localhost:8080/login' ) ) ;
Save and close the file. In a new terminal window or tab, get-go the server:
- node server.js
You volition receive a response indicating that the server is starting:
Output
API is running on http://localhost:8080/login
Visit http://localhost:8080/login
and you lot'll observe your JSON object.
When y'all fetch the token in your browser, you are making a GET
asking, merely when yous submit the login form y'all will be making a Post
request. That'south not a problem. When you set up your route with app.utilise
, Express volition handle all requests the same. In a product awarding, you should be more specific and just allow certain request methods for each route.
At present that y'all accept a running API server, you need to make a request from your login page. Open Login.js
:
- nano src/components/Login/Login.js
In the previous step, you passed a new prop called setToken
to the Login
component. Add in the PropType
from the new prop and destructure the props object to pull out the setToken
prop.
auth-tutorial/src/components/Login/Login.js
import React from 'react' ; import PropTypes from 'prop-types' ; import './Login.css' ; export default role Login ( { setToken } ) { render ( <div className= "login-wrapper" > <h1>Please Log In< /h1> <form> <label> <p>Username< /p> <input blazon= "text" / > < /label> <label> <p>Password< /p> <input type= "password" / > < /label> <div> <push button type= "submit" >Submit< /button> < /div> < /class> < /div> ) } Login.propTypes = { setToken : PropTypes.func.isRequired }
Next, create a local state to capture the Username
and Password
. Since you do not need to manually set data, make the <inputs>
uncontrolled components. You tin find detailed information about uncontrolled components in How To Build Forms in React.
auth-tutorial/src/components/Login/Login.js
import React , { useState } from 'react' ; import PropTypes from 'prop-types' ; import './Login.css' ; export default function Login ( { setToken } ) { const [username, setUserName] = useState ( ) ; const [password, setPassword] = useState ( ) ; return ( <div className= "login-wrapper" > <h1>Please Log In< /h1> <form> <characterization> <p>Username< /p> <input type= "text" onChange= { e => setUserName (e.target.value) } / > < /label> <label> <p>Password< /p> <input blazon= "password" onChange= { e => setPassword (e.target.value) } / > < /characterization> <div> <push button blazon= "submit" >Submit< /push> < /div> < /form> < /div> ) } Login.propTypes = { setToken : PropTypes.func.isRequired } ;
Next, create a part to make a POST
asking to the server. In a large application, you would add together these to a separate directory. In this example, you'll add together the service straight to the component. Check out the tutorial How To Call Web APIs with the useEffect Hook in React for a detailed expect at calling APIs in React components.
Create an async
function called loginUser
. The function will take credentials
every bit an argument, then it will call the fetch
method using the Mail
option:
auth-tutorial/src/components/Login/Login.js
import React, { useState } from 'react' ; import PropTypes from 'prop-types' ; import './Login.css' ; async function loginUser ( credentials ) { return fetch ( 'http://localhost:8080/login' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } , body : JSON . stringify (credentials) } ) . then ( information => information. json ( ) ) } export default role Login ( { setToken } ) { ...
Finally, create a form submit handler called handleSubmit
that will call loginUser
with the username
and countersign
. Call setToken
with a successful event. Call handleSubmit
using the onSubmit
event handler on the <form>
:
auth-tutorial/src/components/Login/Login.js
import React, { useState } from 'react' ; import PropTypes from 'prop-types' ; import './Login.css' ; async function loginUser ( credentials ) { render fetch ( 'http://localhost:8080/login' , { method : 'POST' , headers : { 'Content-Blazon' : 'application/json' } , trunk : JSON . stringify (credentials) } ) . and then ( data => information. json ( ) ) } consign default role Login ( { setToken } ) { const [username, setUserName] = useState ( ) ; const [password, setPassword] = useState ( ) ; const handleSubmit = async eastward => { e. preventDefault ( ) ; const token = await loginUser ( { username, password } ) ; setToken (token) ; } return ( <div className= "login-wrapper" > <h1>Please Log In< /h1> <course onSubmit= {handleSubmit} > <label> <p>Username< /p> <input type= "text" onChange= { eastward => setUserName (e.target.value) } / > < /label> <label> <p>Password< /p> <input type= "countersign" onChange= { e => setPassword (e.target.value) } / > < /label> <div> <button type= "submit" >Submit< /push button> < /div> < /class> < /div> ) } Login.propTypes = { setToken : PropTypes.func.isRequired } ;
Salvage and close the file. Make sure that your local API is withal running, then open up a browser to http://localhost:3000/dashboard
.
Yous volition see the login page instead of the dashboard. Fill out and submit the grade and you lot will receive a web token then redirect to the page for the dashboard.
You lot now have a working local API and an application that requests a token using a username and password. But there is notwithstanding a problem. The token is currently stored using a local state, which means that information technology is stored in JavaScript retentivity. If y'all open a new window, tab, or even just refresh the folio, you will lose the token and the user will need to login once more. This volition exist addressed in the next pace.
In this step y'all created a local API and a login page for your application. You learned how to create a Node server to send a token and how to phone call the server and shop the token from a login component. In the next footstep, y'all'll larn how to shop the user token and then that a session will persist across folio refreshes or tabs.
Step 3 — Storing a User Token with sessionStorage
and localStorage
In this step, you'll shop the user token. You lot'll implement different token storage options and larn the security implications of each approach. Finally, you lot'll learn how different approaches will modify the user feel as the user opens new tabs or closes a session.
By the cease of this step, you lot'll be able to choose a storage arroyo based on the goals for your application.
There are several options for storing tokens. Every option has costs and benefits. In cursory the options are: storing in JavaScript memory, storing in sessionStorage
, storing in localStorage
, and storing in a cookie. The principal trade-off is security. Whatever information that is stored exterior of the retention of the current application is vulnerable to Cross-Site Scripting (XSS) attacks. The danger is that if a malicious user is able to load code into your application, it can access localStorage
, sessionStorage
, and whatever cookie that is besides accessible to your application. The benefit of the non-memory storage methods is that you tin reduce the number of times a user will need to log in to create a better user experience.
This tutorial will cover sessionStorage
and localStorage
, since these are more modern than using cookies.
Session Storage
To test the benefits of storing outside of memory, catechumen the in-retentiveness storage to sessionStorage
. Open App.js
:
- nano src/components/App/App.js
Remove the telephone call to useState
and create two new functions called setToken
and getToken
. Then call getToken
and assign the results to a variable called token
:
auth-tutorial/src/components/App/App.js
import React from 'react' ; import { BrowserRouter, Route, Switch } from 'react-router-dom' ; import './App.css' ; import Dashboard from '../Dashboard/Dashboard' ; import Login from '../Login/Login' ; import Preferences from '../Preferences/Preferences' ; function setToken ( userToken ) { } office getToken ( ) { } function App ( ) { const token = getToken ( ) ; if ( !token) { return <Login setToken= {setToken} / > } return ( <div className= "wrapper" > ... < /div> ) ; } export default App;
Since yous are using the same function and variable names, you lot volition non demand to change any code in the Login
component or the remainder of the App
component.
Inside of setToken
, salvage the userToken
statement to sessionStorage
using the setItem
method. This method takes a fundamental as a starting time statement and a cord as the second argument. That means you'll need to convert the userToken
from an object to a string using the JSON.stringify
function. Call setItem
with a cardinal of token
and the converted object.
auth-tutorial/src/components/App/App.js
import React from 'react' ; import { BrowserRouter, Route, Switch } from 'react-router-dom' ; import './App.css' ; import Dashboard from '../Dashboard/Dashboard' ; import Login from '../Login/Login' ; import Preferences from '../Preferences/Preferences' ; function setToken ( userToken ) { sessionStorage. setItem ( 'token' , JSON . stringify (userToken) ) ; } function getToken ( ) { } function App ( ) { const token = getToken ( ) ; if ( !token) { return <Login setToken= {setToken} / > } return ( <div className= "wrapper" > ... < /div> ) ; } export default App;
Relieve the file. When you do the browser will reload. If you type in a username and countersign and submit, the browser will still render the login page, but if you look within your browser panel tools, you'll find the token is stored in sessionStorage
. This epitome is from Firefox, merely y'all'll discover the aforementioned results in Chrome or other modern browsers.
Now y'all need to retrieve the token to render the correct page. Within the getToken
function, call sessionStorage.getItem
. This method takes a key
every bit an argument and returns the string value. Catechumen the cord to an object using JSON.parse
, so render the value of token
:
auth-tutorial/src/components/App/App.js
import React from 'react' ; import { BrowserRouter, Route, Switch } from 'react-router-dom' ; import './App.css' ; import Dashboard from '../Dashboard/Dashboard' ; import Login from '../Login/Login' ; import Preferences from '../Preferences/Preferences' ; function setToken ( userToken ) { sessionStorage. setItem ( 'token' , JSON . stringify (userToken) ) ; } part getToken ( ) { const tokenString = sessionStorage. getItem ( 'token' ) ; const userToken = JSON . parse (tokenString) ; return userToken?.token } function App ( ) { const token = getToken ( ) ; if ( !token) { return <Login setToken= {setToken} / > } return ( <div className= "wrapper" > ... < /div> ) ; } export default App;
You need to utilise the optional chaining operator—?.
—when accessing the token
property considering when you lot commencement access the application, the value of sessionStorage.getItem('token')
will be undefined
. If you endeavor to admission a property, you will generate an mistake.
Save and close the file. In this case, you already have a token stored, so when the browser refreshes, you volition navigate to the individual pages:
Clear out the token by either deleting the token in the Storage tab in your developer tools or past typing sessionStorage.clear()
in your developer console.
At that place's a little problem at present. When you lot log in, the browser saves the token, just y'all still meet the login page.
The problem is your code never alerts React that the token retrieval was successful. You'll notwithstanding need to set some state that will trigger a re-render when the data changes. Similar about problems in React, there are multiple ways to solve it. One of the most elegant and reusable is to create a custom Hook.
Creating a Custom Token Hook
A custom Hook is a part that wraps custom logic. A custom Hook commonly wraps one or more born React Hooks along with custom implementations. The master advantage of a custom Hook is that yous can remove the implementation logic from the component and you lot can reuse it across multiple components.
By convention, custom Hooks start with the keyword use*
.
Open a new file in the App
directory called useToken.js
:
- nano src/components/App/useToken.js
This will be a minor Hook and would be fine if you defined it direct in App.js
. Merely moving the custom Hook to a unlike file will show how Hooks work outside of a component. If you start to reuse this Hook beyond multiple components, yous might as well want to move it to a separate directory.
Inside useToken.js
, import useState
from react
. Notice that you do not need to import React
since you will have no JSX in the file. Create and consign a function chosen useToken
. Inside this function, use the useState
Hook to create a token
state and a setToken
role:
auth-tutorial/src/components/App/useToken.js
import { useState } from 'react' ; export default office useToken ( ) { const [token, setToken] = useState ( ) ; }
Next, copy the getToken
office to useHook
and convert it to an pointer role, since you placed it inside useToken
. You could get out the role as a standard, named function, but information technology can be easier to read when top-level functions are standard and internal functions are arrow functions. However, each squad volition be different. Choose one way and stick with it.
Place getToken
earlier the state proclamation, then initialize useState
with getToken
. This volition fetch the token and ready it equally the initial state:
auth-tutorial/src/components/App/useToken.js
import { useState } from 'react' ; export default office useToken ( ) { const getToken = ( ) => { const tokenString = sessionStorage. getItem ( 'token' ) ; const userToken = JSON . parse (tokenString) ; return userToken?.token } ; const [token, setToken] = useState ( getToken ( ) ) ; }
Next, copy the setToken
function from App.js
. Catechumen to an arrow function and name the new function saveToken
. In addition to saving the token to sessionStorage
, save the token to state by calling setToken
:
auth-tutorial/src/components/App/useToken.js
import { useState } from 'react' ; export default office useToken ( ) { const getToken = ( ) => { const tokenString = sessionStorage. getItem ( 'token' ) ; const userToken = JSON . parse (tokenString) ; return userToken?.token } ; const [token, setToken] = useState ( getToken ( ) ) ; const saveToken = userToken => { sessionStorage. setItem ( 'token' , JSON . stringify (userToken) ) ; setToken (userToken.token) ; } ; }
Finally, render an object that contains the token
and saveToken
set to the setToken
property name. This will give the component the same interface. Y'all can too return the values as an array, only an object will requite users a chance to destructure only the values they desire if you reuse this in another component.
auth-tutorial/src/components/App/useToken.js
import { useState } from 'react' ; export default function useToken ( ) { const getToken = ( ) => { const tokenString = sessionStorage. getItem ( 'token' ) ; const userToken = JSON . parse (tokenString) ; return userToken?.token } ; const [token, setToken] = useState ( getToken ( ) ) ; const saveToken = userToken => { sessionStorage. setItem ( 'token' , JSON . stringify (userToken) ) ; setToken (userToken.token) ; } ; return { setToken : saveToken, token } }
Save and close the file.
Next, open App.js
:
- nano src/components/App/App.js
Remove the getToken
and setToken
functions. Then import useToken
and call the part destructuring the setToken
and token
values. You can besides remove the import of useState
since you are no longer using the Hook:
auth-tutorial/src/components/App/App.js
import React from 'react' ; import { BrowserRouter, Road, Switch } from 'react-router-dom' ; import './App.css' ; import Dashboard from '../Dashboard/Dashboard' ; import Login from '../Login/Login' ; import Preferences from '../Preferences/Preferences' ; import useToken from './useToken' ; office App ( ) { const { token, setToken } = useToken ( ) ; if ( !token) { return <Login setToken= {setToken} / > } render ( <div className= "wrapper" > <h1>Application< /h1> <BrowserRouter> <Switch> <Route path= "/dashboard" > <Dashboard / > < /Route> <Route path= "/preferences" > <Preferences / > < /Road> < /Switch> < /BrowserRouter> < /div> ) ; } export default App;
Save and close the file. When you do, the browser will refresh, and when you log in, you will immediately go to the folio. This is happening because you are calling useState
in your custom Hook, which volition trigger a component re-return:
Yous at present have a custom Hook to store your token in sessionStorage
. Now you can refresh your page and the user will remain logged in. But if you try to open up the application in another tab, the user will be logged out. sessionStorage
belongs only to the specific window session. Any data will not exist bachelor in a new tab and will exist lost when the agile tab is closed. If you lot desire to save the token across tabs, yous'll need to convert to localStorage
.
Using localStorage
to Save Information Beyond Windows
Unlike sessionStorage
, localStorage
will salvage data even afterwards the session ends. This can exist more convenient, since information technology lets users open multiple windows and tabs without a new login, but it does have some security problems. If the user shares their estimator, they will remain logged in to the application even though they close the browser. It will exist the user'south responsibility to explicitly log out. The next user would have immediate admission to the application without a login. It's a risk, just the convenience may be worth it for some applications.
To convert to localStorage
, open useToken.js
:
- nano src/components/App/useToken.js
And then modify every reference of sessionStorage
to localStorage
. The methods you telephone call will exist the same:
auth-tutorial/src/components/App/useToken.js
import { useState } from 'react' ; export default function useToken ( ) { const getToken = ( ) => { const tokenString = localStorage . getItem ( 'token' ) ; const userToken = JSON . parse (tokenString) ; return userToken?.token } ; const [token, setToken] = useState ( getToken ( ) ) ; const saveToken = userToken => { localStorage . setItem ( 'token' , JSON . stringify (userToken) ) ; setToken (userToken.token) ; } ; return { setToken : saveToken, token } }
Save the file. When you do, the browser volition refresh. Yous will need to log in again since there is no token all the same in localStorage
, but after y'all do, you will remain logged in when you lot open a new tab.
In this step, y'all saved tokens with sessionStorage
and localStorage
. You also created a custom Hook to trigger a component re-render and to move component logic to a separate part. You besides learned about how sessionStorage
and localStorage
affect the user'due south ability to start new sessions without login.
Determination
Authentication is a crucial requirement of many applications. The mixture of security concerns and user experience tin can exist intimidating, but if you focus on validating data and rendering components at the right time, it can become a lightweight procedure.
Each storage solution offers singled-out advantages and disadvantages. Your choice may change as your application evolves. By moving your component logic into an abstract custom Hook, you give yourself the ability to refactor without disrupting existing components.
If you would like to read more React tutorials, bank check out our React Topic folio, or render to the How To Code in React.js serial folio.
Source: https://www.digitalocean.com/community/tutorials/how-to-add-login-authentication-to-react-applications
0 Response to "Session Got Sent and Again Went Missing React"
Post a Comment