Let’s walk through the JWT process I’ve implemented in my application, how the flow works.
My Backend API GitHub link.
📝 A Quick Refresher on JWTs
A JSON Web Token (JWT) is a standard I’m using to securely transmit information between my client and server. It’s essentially a signed, self-contained JSON object.
Because it’s signed with a secret key that only my server knows, I can trust the information within it.
📦 JWT Structure
It has three parts:
-
Header
Metadata about the token (e.g., algorithm
HS256). -
Payload
The data I want to send (claims), like
userId,email, androle, plus standard claims likeexp(expiration time). - Signature Ensures the token’s integrity. Created using the header, payload, and my secret key.
🚀 The JWT Flow I’ve Built
Here is the step-by-step flow of how I am using JWTs in my code.
✅ Step 1: Generate Token on Login
This is where my user gets their credentials for accessing protected parts of the API.
Login Request
A user submits their email and password to my/api/v1/auth/loginendpoint.Service Logic (
auth.service.ts)
-
credentialsLoginservice finds the user in the database. - Securely compares the submitted password with the stored hash using
bcryptjs.compare. - Creates a
jwtPayloadobject with only necessary, non-sensitive data.
const jwtPayload = {
userId: isUserExist._id,
email: isUserExist.email,
role: isUserExist.role,
};
- Signing the Token (
utils/jwt.ts)
I call my generateToken utility. It's great that I've abstracted this into a separate utility file to keep my code clean.
Uses
jsonwebtokento sign the payload withJWT_ACCESS_SECRETand sets an expiration time from my.envvariables..
- Sending the Token
- Sends the newly created
accessTokenback to the client. The client stores it for future requests.
🔐 Step 2: Client Accesses a Protected Route
Now, the user wants to do something that requires authentication, like updating their profile.
The client sends a PATCH request to
/api/v1/user/:idwith theaccessTokenin the Authorization header, typically asBearer <token>.:Middleware Intercepts (
user.route.ts)
- The request passes through the
checkAuthmiddleware before hitting the controller.
🛡 Step 3: Verify the Token and Authorize the User (checkAuth.ts)
This middleware is the heart of the API’s security.
A Flexible Higher-Order Function
I designedcheckAuthas a higher-order function. It accepts a list of roles (...authRoles) and returns an Express middleware function. This makes it incredibly powerful and reusable. For instance, to protect an admin-only route, I simply usecheckAuth(Role.ADMIN, Role.SUPER_ADMIN).Token Extraction
My middleware first looks for the Authorization header and extracts the token.Token Verification
It then calls myverifyTokenutility function.
This function usesjwt.verify()with the sameJWT_ACCESS_SECRET. This single call verifies both the signature (ensuring it’s not tampered with) and the expiration date.
If the token is invalid for any reason,jwt.verify()throws an error, which my catch block sends to myglobalErrorHandler.Role-Based Authorization
If the token is valid, I then check if the user’s role (from the decoded token) is included in theauthRolesI passed to the middleware. If not, I throw a "Forbidden" error.Attaching the User to the Request
This is a key pattern I’ve implemented. After successful verification and authorization, I attach the entire decoded payload to the ExpressRequestobject.
req.user = verifiedToken; // declared globally in src/app/interfaces/index.d.ts
🏗 Step 4: Controller and Service Use Authenticated User
Because of the work I did in the middleware, my controller logic is now much cleaner.
-
Accessing the User in the Controller (
user.controller.ts) In my updateUser controller, I can now trust that req.user exists and contains the authenticated user's data. I don't need to re-verify the token here.
const verifiedToken = req.user;
This is fully type-safe because I cleverly extended the global Express Request type in backend-tour-managemnet-system\src\app\interfaces\index.d.ts.
-
Service-Layer Logic (
user.service.ts) I pass this trustedverifiedTokento my service layer, where I can implement fine-grained business logic, such as checking if anADMINis trying to modify a SUPER_ADMIN.
This entire flow is robust and secure.
Top comments (0)