s5iri24, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
Refresh Auth Token Rotation (Node
js & React ) — Part 1
Tékos Bence - Follow
14 min read - Mar 18, 2024
ss Qi tea
>
@
a.
‘Auth Token Rotation ( Node js & React js )
These days, security is increasingly crucial. So, we need to be clear about at
least the basics of security concerns and tips & tricks. Many websites out there
have weak authentication, and even if your application doesn’t contain any
sensitive information, such as just a mail address, you need to respect your
users and provide minimal security. In this article, I will demonstrate a simple
JWT token-based authentication system with access and refresh tokens. We
will use a Node js backend, React for the frontend, and MongoDB. If you aren’t
hitps:fmedium.com/@tokosbexiauth-token-rolation-node-js-reactpat-1-b&3a8747To4d 11assi4, 2208 Ratresh Auth Token Roaon (Node & React) — Pat [hy Toko Bence [ Medum
familiar with these technologies, please take some time to familiarize yourself
with them before diving in. However, if you have some JavaScript skills and
basic REST knowledge, then come along and delve into this with me. :)
Let’s start by setting up a Node,js backend. First, initialize the Node
application:
apm init
Next, install Express:
apm install express
Here’s the file and folder hierarchy we'll use in the backend:
hitps:fmedium.com/@tokosbexiauth-token-rolation-node-js-reactpat-1-b&3a8747To4ds5iri24, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
Rarer
ae
feeiite sis
Trott
Re
Baltes a
Folder struct
Create the server,js file:
c
require ("express");
5000;
const port
nst app = express (Ii
app.Listen (port, () =>
g(Server running on port ${port)*);
Now, let’s add a database for our app. Install MongoDB. If you're not familiar,
here is a useful link.
hitps:fmedium.com/@tokosbexiauth-token-rolation-node-js-reactpat-1-b&3a8747To4d 3415iri24, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
apm install mongoose
Create the database connection file:
nst mongoose = require ("mongoose") ;
const db = mongoose
i
hen (() => console. 1og ("Connected to MongoD3.
tech ((e
connect ("mong 0.0.1/TokenRotation") //TokenRotation is the db name
")
‘Could not connect to MongoDB..
yere))4
module.exports = db;
Don’t forget to add the database connection to servers:
const db = require("./dbconnection'
Now, let’s install some additional packages:
apm install dotenv
npm install berypt
Now, let’s create the User model:
-ntps:timedumcom/@tokosbex/auth-token-rotaton-nade-je-react-part-t-b83a8747To4d
415iri24, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
const jwt = require ("jsonwebtoken");
const mongoose = require ("mongoose") ;
require ("dotenv") .config()
nst userSchema = new mongoose. Schema ({
: String,
: true,
unique: true,
»
password: {
type: String,
: true,
refreshtoken: [String],
De
userSchena methods .generateAuthToken = function () (
const accesstoken = jwe.sign(
( id: this. id },
process.env.ACCESS_SECRET_KEY,
10s",
const refreshtoken = jwt.sign(
{ Ad: this. id },
process.env.REFRESI_SECRET_KEY,
( expixesin: "1d" }
Me
const tokens = { accesstoken: accesstoken, refreshtoken:
return tokens;
h
const User = mongoose.model ("User", userSchena) ;
exports.User = User;
We define a schema for our user data in MongoDB.
Each user document will have an email, password, and an array to store refresh
tokens.
-ntps:timedumcom/@tokosbex/auth-token-rotaton-nade-je-react-part-t-b83a8747To4d
refreshtoxen };s5iri24, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
We add a method to our user schema called generateauthToken .
* This method creates both access and refresh tokens using
the jsonwebtoken package.
* Access token expires in 10 seconds and contains the user’s _ia.
¢ Refresh token expires in 1 day and also contains the user’s _ia.
We create a Mongoose model named user based on the user schema.
* This model will be used to interact with the MongoDB collection for users.
* We export the user model for use in other parts of our application.
AppError & TryCatch
We're enhancing our codebase with two utility functions for better error
handling: apperror and trycaten.
The apperror class is designed to encapsulate error information in a structured
manner. It extends the native error class and adds custom properties
like errorcode and statuscode , allowing us to categorize errors and respond
with appropriate HTTP status codes. This ensures consistency in error
handling across our application.
srror extends Error {
rorCode,
hitps:fmedium.com/@tokosbex/auth-token-rolaion-node-js-eactpat-1-b&3a8747To4d 6115/7124, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
medule.exports = AppError;
The tryCatch function provides a convenient way to handle errors within our
asynchronous controller functions. It takes a controller function as input and
returns a new asynchronous function. This new function wraps the original
controller function with a try-catch block. If an error occurs during the
execution of the controller function, it’s caught, logged, and passed to the
Express next function for centralized error handling. This abstraction
simplifies error handling in our route handlers, promoting cleaner and more
maintainable code.
s.tryCatch = (controller) => async (req, res, next) => {
controller (req, res);
h (error)
ole. log (error) ;
return next (error) ;
By incorporating these utility functions into our codebase, we improve the
readability of our error handling mechanism, ultimately enhancing the overall
reliability and user experience of our application.
Routes
Next, we'll design the endpoints for user authentication and session
management, including SignIn, SignUp, RefreshToken, and LogOut. To begin,
let’s lay out the routes for these functionalities.
hitps:fmedium.com/@tokosbexiauth-token-rolation-node-js-reactpat-1-b&3a8747To4d mi15iri24, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tékos Bence [Medium
const express = require
const authController
const verifyJW? = require("../
const router = express.Router ();
//Puth based s
ex-post ("/sigaUp",
t("/signin",
/refresh", a
/logout™,
module.exports = router;
Ina Node js application, services and controllers are two essential components
that help organize and structure the codebase, promoting separation of
concerns and maintainability.
1. Services: Services encapsulate business logic and data manipulation
operations. They are responsible for performing specific tasks or operations
related to a particular domain within the application. Services abstract away
the details of how tasks are implemented, allowing controllers to remain
Jean and focused on handling HTTP requests and responses. Services are
often reusable across multiple parts of the application and facilitate testing
by isolating business logic from external dependencies.
n
. Controllers: Controllers handle incoming HTTP requests and define the
application’s endpoints. They extract data from incoming requests, interact
with services to perform necessary operations, and send back appropriate
responses to clients. Controllers serve as the bridge between the client-side
interface (such as a web browser or mobile app) and the underlying
application logic. They organize and delegate tasks to services, ensuring
that business logic remains decoupled from the presentation layer.
Controllers are typically responsible for routing and request/response
handling, making them a crucial component of the application’s request-
response cycle.
hitps:fmedium.com/@tokosbexiauth-token-rolation-node-js-reactpat-1-b&3a8747To4d anis5iri24, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
Let’s start by implementing the SignUp functionality:
const berypt re ("berypt") +
const jwt = require ("jsonwebtoken") ;
const { tryCatch } = require("../.
const AppError = require("../../utils/AppError") ;
const authServices = require ("./services");
const { User } = require("../../Models/user") +
exports.SignUp = tryCatch(async (xeq, res) => (
const { email, password ) = req,
onst user = await User.find({ ema
if (luser)
throw new AppBrror (
409,
“Email address already exits in the database!",
409
try {
const response = await authServices.signUp(email, password) ;
const accessToken = response.
const refreshToken = response
res.cookie("jwt", refreshToken,
httponl
maxAge: 24 * 60 * 60 * 1000,
ns
res. status (200) . jsoi
nt
console. log (err
accessTokent accessToken }) .end()7
catch (
us (err. statusCode) .}son (err.message) .end() ;
This code snippet defines a controller function SignUp, responsible for
handling user registration requests in a Node.js application. It imports
necessary modules such as berypt for password hashing and jsonwebtoken for
token generation, along with custom error handling utilities. Upon receiving a
request, it extracts the email and password from the request body and checks
for existing users with the same email. If no duplicate email is found, it calls a
-ntps:timedumcom/@tokosbex/auth-token-rotaton-nade-je-react-part-t-b83a8747To4d ens51724, 2:08 Refresh Auth Token Rotation (Node js & React) — Part 1 by Tékos Bence | Medium
SignUp service to handle user creation, generates access and refresh tokens,
sets the refresh token in an HTTP-only cookie for security, and sends the
access token in the response. Any errors that occur during the SignUp process
are caught and handled appropriately with error status codes and messages.
‘The SignUp service:
ast berypt = require ("berypt")
const ( User ) = require("../,./Models/user") ;
const { UserProfile } = require("../../Model profile") ;
const mongoose = require ("mongoose");
const Obj:
‘d= mongoose. Types .Objec’
const signUp = async (email, password) => {
const newUser = new User ({
password: password,
(0;
hash (newJser.password, sali
newlser.password = await berypt
ens = newUser.generateAuthToken () 7
tokens. refreshtoken;
token: tokens,
return data;
This service function signup is responsible for creating a new user in the
application. It takes an email and password as parameters. Inside the function,
anew user object is created with the provided email and password, and an
empty refresh token field. The password is then hashed using berypt for
security. After generating authentication tokens using
-ntps:timedumcom/@tokosbex/auth-token-rotaton-nade-je-react-part-t-b83a8747To4d 012151724, 2:08 Refresh Auth Token Rotation (Node js & React) — Part 1 by Tékos Bence | Medium
the generateauthtoken method of the user model, which is responsible for
creating access and refresh tokens, the refresh token is assigned to the user
object. Finally, the new user is saved to the database, and an object containing
the generated tokens and the user's ID is returned. This function ensures that
user passwords are securely hashed before being stored in the database and
provides authentication tokens for the newly created user.
With the same schema, we'll now implement the SignIn functionality:
exports.SignIn = tryCateh (async (eq, res}
cookies cookies;
{ email, password } = req.body;
const user = await User.findone({ email: email
F (tuser)
throw new AppError (
404,
“Email address not found. Please check your email and try again.",
404
const valiPassword = await berypt.compare (password, user.password) ;
f (WvalidPassword) {
throw new AppError (
401,
Incorrect password. Please de
401
%
ble-check your password and try again.",
try {
let newRefreshTokenArray = "";
// Check if user has an existing refresh token
if (!cookies?.jwt)
refreshToken = user.refreshtoken;
} else {
refreshToken
cookies. jwts
const foundToken = await User.findOne({ refreshToken }) .exec();
if (1foundToken)
console. log ("Attempted refresh token reuse at login!");
// Lf the token is not found in the database, clear out the cookie
-ntps:simedumcom/@tokosbex/auth-token-rotation-nade-je-react-part-t-83a8747To4d sa15/7124, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
res.clearCookie("jwt", { httpOnly: true });
refreshToken =";
ices.signin (user, eshTokenArray) ;
const accessToken = response. token.accesstoken;
refreshToken = response. token. refreshtoken;
se. profilePicy
const profilePic = respot
res.cookie("jwi", ref:
httponly: true,
24 * 60 * 60 * 1000,
maxg:
Ds
return res
-status (200)
scookie("jwi", refreshToken, {
nttpon:
maxage: 26 * 60 * 60 * 1000,
»
json({ accessToken: accessToken, profile?!
send
catch (err) {
console. log (err) ;
true,
profilePic })
return res.status (err.statusCode) . json (err.message) .end() ;
This code segment implements the SignIn functionality in a Nodejs
application, handling user authentication securely. It first extracts the user’s
credentials from the request body and retrieves the user from the database
based on the provided email. If the user is not found or the password does not
match, it throws custom errors indicating the corresponding issues. It then
checks for an existing refresh token for the user and verifies its validity to
prevent token reuse. Upon successful authentication, it calls a service function
to generate new access and refresh tokens and retrieves the user’s profile
picture. The new refresh token is set in an HTTP-only cookie for security, and
the access token and profile picture are sent back in the response. Any errors
encountered during the process are appropriately handled and returned as
error responses with the corresponding status codes and messages.
-ntps:timedumcom/@tokosbex/auth-token-rotaton-nade-je-react-part-t-b83a8747To4d ra15iri24, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
const signin = async (user, newRefreshTokenArray} => {
const token = user.generateAuthToken();
user.refreshtoken = [...newRefreshTokenArray, token.refreshtoken];
await user.save();
const data =
token: token,
‘
This service function signIn handles the creation of authentication tokens and
retrieval of user profile information upon successful user authentication. It
takes the authenticated user object and an array of new refresh tokens as
parameters. Then, it generates authentication tokens using the
generateAuthToken method of the user object. The new refresh token is added
to the user’s refreshtoken array, and the user object is saved back to the
database. Finally, it constructs and returns an object containing the generated
tokens and the user’s ID. This function ensures that authentication tokens are
created and stored securely, and user information is retrieved and provided.
upon successful authentication.
Okay until here is a basic auth flow we just need the Logout functionality:
exports:
ut = tryCatch (asyne (req, res)
//on client, also delete the accessToken
mist cookies = req.cooki:
if (Icookies?.jwt) return res. sends
const refreshToken = cookies.jwt;
/fis refresh token in db?
const foundUser = await User.findone({ refreshtoken: refreshToken });
if (!founduser)
-ntps:simedumcom/@tokosbex/auth-token-rotation-nade-je-react-part-t-83a8747To4d 1972115/7124, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
res.cle: { httponly: De
204) ¢
/ (Delete shToken in db
found oken = foundUser. refreshtoken. filter (
(ct) => rt [== refreshToken
await foundUser.save();
Cookie("jwt", { httponly: true });
cats (204) ;
This code snippet defines the 1ogout controller function responsible for
handling user logout requests in a Node js application. It utilizes
the trycatch utility for error handling. The function first checks if the user has
arefresh token stored in a cookie. If not, it sends a status code of 204 (No
Content) to indicate that no action is required. If a refresh token is found, the
function queries the database to verify its existence. If the refresh token is not
found in the database, it clears the refresh token cookie and sends a status
code of 204. Otherwise, it removes the refresh token from the user's stored.
refresh tokens, saves the updated user object back to the database, clears the
refresh token cookie, and sends a status code of 204 to indicate successful
logout. This function ensures proper handling of user logout requests by
managing refresh tokens securely and clearing associated cookies.
The Refresh endpoint
I’m sure you recognized the refresh token in all functionality. We are not used
yet always just sending or deleting. Let’s talk a little bit about the refresh
token.
Arefresh token is a security token used in authentication systems to obtain
new access tokens once they expire. Unlike access tokens, which have a short
lifespan, refresh tokens are long-lived and typically last for days or even weeks.
hitps:fmedium.com/@tokosbexiauth-token-rolation-node-js-reactpat-1-b&3a8747To4d 141251724, 2:08 Refresh Auth Token Rotation (Node js & React) — Part 1 by Tékos Bence | Medium
When an access token expires, the refresh token can be used to request a new
access token without requiring the user to log in again. This enhances security
by minimizing the exposure of sensitive credentials and reducing the
frequency of user authentication. Refresh tokens are securely stored on the
client-side, often in HTTP-only cookies, and are exchanged for new access
tokens through a secure authentication flow.
So, does that clarify things a bit? For enhance this we need a refresh token
endpoint what we can call “silent” when the user access key is expired.
exports. refreshToken = tryCateh(asyne (req, res) => {
const cookies = req.cookies;
sjwt) return res.sey
wtp
Lf (Icookie
(401) 5
const refreshToken = cookies
LearCookie("jwt", { nttpOnly: true })7
const foundUser = avait User.findone({ vefreshtoken: refreshToken });
if (!foundvser)
refreshToken,
process.env.REFRESH SECRET KEY,
async (err, decoded) => (
Af (err) return res.sendStatus (403); //Forbidden
const hackedUser ~ await User.findOne({ username: decoded. id })¢
hackedUser.refreshtoken = [17
const result = await hackedUser.save();
)
return res.sendstatus (403);
const newRefreshTokenArray = £
freshtoken. filter (
(xt) => refreshToken
/fevaluate dwt
jut.verify(
refreshToken,
process.env. ECRET_KEY,
asyne (err, decoded)
if le
fou
reshtoken = [...newRefreshTokerArray];
await foundUser.save();
const resu
)
-ntps:simedumcom/@tokosbex/auth-token-rotation-nade-je-react-part-t-83a8747To4d 151215/7124, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
if (err || found id. 1() !== decoded. id) (
return 403)
)
//refreshtoken still valid
const accessToken = jwt.sign(
id: decoded. id },
cess env-AC SECRET_KEY,
expiresIn: "10s" }
foundUser.refreshtoken = hTokenArray, newRefreshToken];
const result
foundy
res.cookie("jut", newRefreshToken,
httponly: true,
maxage: 24 * 60 * 60 * 1000,
atus (200) json (access Token) ;
1. Checking for Refresh Token: We start by checking if there’s a refresh
token stored in a cookie named “jwt.” If not, we respond with a status code
of 401 (Unauthorized), indicating that the user needs to authenticate.
2. Clearing Cookie: Regardless of whether a refresh token is found, we clear
the “jwt” cookie to ensure security.
3. Finding User: We then search for the user associated with the refresh
token in our database. If the user isn’t found, we proceed to verify the
refresh token’s integrity.
4. Verifying Refresh Token: Using a secret key stored in our environment
variables, we verify the refresh token. If it’s invalid or expired, we respond
with a status code of 403 (Forbidden). Additionally, we locate the user
whose token was used for verification and remove all refresh tokens
sociated with them, preventing any potential misuse.
-ntps:timedumcom/@tokosbex/auth-token-rotaton-nade-je-react-part-t-b83a8747To4d 1612151724, 2:08 Refresh Auth Token Rotation (Node js & React) — Part 1 by Tékos Bence | Medium
5. Refreshing Tokens: Assuming the refresh token is valid and associated
with a user, we proceed to refresh the access and refresh tokens. We
generate a new access token with a short expiration time (here, 10 second)
and a new refresh token with a longer expiration time (here, 1 day). We
update the user’s refresh token array with the new refresh token and save
the changes to the database.
Setting New Cookie: Finally, we set the new refresh token as a cookie,
ensuring it’s sent securely with HTTP-only flag and a maximum age of 24
hours. We respond with a status code of 200 (OK) along with the new
access token.
We also need a secure endpoint in this case we get the user email from the
database:
res) => {
userId = req.parans. id;
d: userId 1)5
t foundUser = await User. findone ({
: foundUser.email,
-status (200) json (data) -end();
We also need to add to our routes:
(math bi
hitps:fmedium.com/@tokosbex/auth-token-rolaion-node-js-eactpat-1-b&3a8747To4d Tie15/7124, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
ign) +
gnin", authContreller.SignIn);
Lier
post ("/signUp", authContr
post ("
reget ("/refresh", authController
rollé
eshToken) ;
Logout
/getUser/:id", verifyJWT, authControLlé
nodule.exports = router;
Here I need to talk about the verifyJWT middleware:
const jwt = require("jsonwebtoken")
require ("dotenv") .config();
const verifyJWP = (req, res, next)
const authHeader = req.headers["authorization"]s
if (Jauthieader) return res.sendstatus (401);
console. log (authHeader)
const token = authHeader. split ("Bearer ") [1];
onsole.log ("access secret", process.env.ACCESS_SECRET_KEY) ;
WV -ACCESS_SECRET,
t.verify (token, process. (err, decoded) => {
console. log (decoded) ;
if (erz)
console. leg (err) ;
return res.status(403).json({ error: "Forbidden: JW? token expired!" });
}
req.user = decoded.username;
next () 5
module.exports = verify:
In this code, we're defining a middleware function named verifyJWT,
responsible for verifying JSON Web Tokens (JWT) used for authentication in
our application. First, we import the jsonwebtoken package to handle JWT
verification and the dotenv package to access environment variables securely.
The verifyJWT function takes three parameters: req (request), res (response),
-ntps:timedumcom/@tokosbex/auth-token-rotaton-nade-je-react-part-t-b83a8747To4d 812151724, 2:08 Refresh Auth Token Rotation (Node js & React) — Part 1 by Tékos Bence | Medium
and next, allowing it to operate as middleware in Express applications. Within
the function, we extract the JWT token from the Authorization header of the
incoming request. If the header is missing or malformed, we immediately
respond with a status code of 401 (Unauthorized). Otherwise, we split the
header to isolate the token from the “Bearer “ prefix. We then use jwt.verify to
decode and verify the token against the access secret key stored in the
environment variables. If the verification fails (e.g., due to token expiration or
invalid signature), we respond with a status code of 403 (Forbidden) and an
error message. Otherwise, if the token is valid, we extract the decoded payload
(which typically contains user information) and attach it to the request object
as req.user. Finally, we call the next function to pass control to the next
middleware or route handler in the request-response cycle. This middleware
ensures that only requests with valid JWT tokens are allowed to access
protected routes, enhancing the security of our application.
After we created all things we need some aditional setup in our servers for
cors and cookie parsing:
userRoutes = require("./apis/use
const frontendURI = “http: //localhost:
app.use (bodyParser.json({ Limit: "Smb" 1))5
app.use (cookieParser ());
app.use (
ndURT, // Replace with your fre
/ Allo
true, /) ientials
es, authorization headers)
app.use("/users", userRout:
hitps:fmedium.com/@tokosbex/auth-token-rolaion-node-js-eactpat-1-b&3a8747To4d 1912115/7124, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
. cors (Cross-Origin Resource Sharing):
Cors middleware is responsible for enabling Cross-Origin Resource Sharing
in our application. It allows resources on our server to be requested from
another domain (origin) than the one it originated from. This is crucial for
client-side web applications that make requests to servers on different
domains.
In our code, we’re configuring cors with specific options:
origin : Specifies the allowed origin or origins of cross-origin requests. In
this case, we're allowing requests from a specific frontend URI
(srontenaurt ) which is "http://localhost:5173".
credentials : Indicates whether or not the server should include credentials
such as cookies or authorization headers in cross-origin requests. Setting it
to true allows the client to include such credentials.
2. bodyParser:
The bodyParser middleware parses incoming request bodies and makes the
parsed data available under the req.body property. It's essential for
processing data sent from clients, especially when dealing with POST or
PUT requests.
In our code, we’re using bodyParser.json() to parse JSON-encoded request
bodies. We're also setting a limit of 5MB for the request body size to
prevent potential denial-of-service attacks by limiting the amount of data
that can be sent in a single request.
hitps:fmedium.com/@tokosbexiauth-token-rolation-node-js-reactpat-1-b&3a8747To4d 20124s5iri24, 22:08 Rofresh Auth Token Rotation (Node js & React ) — Part 1 | by Tokos Bence [Medium
3. cookieParser:
* CookieParser middleware parses cookies attached to the client’s request
and makes them available under the req.cookies property. It's essential for
handling cookies sent by the client, which are often used for session
management, authentication, and other purposes.
+ In our code, we're simply using the default settings of cookieParser without
any additional configuration.
Now that we’ve covered the backend implementation, it’s time to tie it all
together by creating a minimal frontend for our application. By building a
frontend interface, we'll be able to interact with the backend functionalities
we've just developed. Let’s start by setting up a basic frontend structure using
React. We'll create user interface components to interact with the
authentication endpoints we've implemented on the backend, such as SignUp,
SignIn, RefreshToken, and LogOut. Additionally, we'll utilize tools like Axios to
make HTTP requests to our backend API and handle user authentication flows
seamlessly. So let’s roll up our sleeves and dive into the frontend development
process to complement our backend architecture!
If you want to verify and ensure that everything is done correctly, please visit
my GitHub repository for this code.
Looking forward to seeing you in part 2!
hitps:fmedium.com/@tokosbexiauth-token-rolation-node-js-reactpat-1-b&3a8747To4d aie