0% found this document useful (0 votes)
46 views

Refresh Auth Token Rotation (Node Js & React) - Part 1

Refresh Auth Token Rotation (Node js & React ) - Part 1

Uploaded by

Ronny Camacho
Copyright
© © All Rights Reserved
Available Formats
Download as PDF or read online on Scribd
0% found this document useful (0 votes)
46 views

Refresh Auth Token Rotation (Node Js & React) - Part 1

Refresh Auth Token Rotation (Node js & React ) - Part 1

Uploaded by

Ronny Camacho
Copyright
© © All Rights Reserved
Available Formats
Download as PDF or read online on Scribd
You are on page 1/ 21
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 11a ssi4, 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&3a8747To4d s5iri24, 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 34 15iri24, 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 4 15iri24, 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 61 15/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 mi 15iri24, 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 ani s5iri24, 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 ens 51724, 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 0121 51724, 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 sa 15/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 ra 15iri24, 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 19721 15/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 1412 51724, 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 1512 15/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 16121 51724, 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 Tie 15/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 8121 51724, 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 19121 15/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 20124 s5iri24, 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

You might also like