Topic 023: Building a Token Management System and Handling Conditional Redirects in Node.js: A Step-by-Step Guide to Expiration, Logging, and User Workflow Management
Let’s break this down step by step and build a Node.js code that handles tokens, checks expiration, and fetches new tokens when needed. We’ll also incorporate logging via console.log
to track the operations.
?.
):
record?.value
means if record
exists, return record.value
; otherwise, return undefined
. This prevents errors when trying to access properties of null
or undefined
.Date.now().valueOf() / 1000
gives the current time in seconds (not milliseconds, so / 1000
is used).exp
refers to the expiration time of the token, which is a standard field in JWT (JSON Web Token). We compare it with the current time to check if the token is expired.Decoding JWT:
jwt.decode()
, we extract the payload from the token, which contains the exp
field (the token’s expiration time).decodetoken.payload.exp * 1000
multiplies the expiration time (exp
) by 1000 to convert it into milliseconds.jsonwebtoken
(for decoding JWTs)const jwt = require("jsonwebtoken"); // Install using npm: npm install jsonwebtoken
// Simulated token record from MOD OIDC URL
let record = {
value: "your.jwt.token.here", // This is a placeholder JWT token
};
// Function to simulate token fetching and caching
function handleToken() {
const token = record?.value; // Optional chaining to get the token value
if (!token) {
console.log("No token found in record.");
return;
}
// Get the current time in seconds
const currTime = Math.floor(Date.now() / 1000);
console.log(`Current time (in seconds): ${currTime}`);
// Decode the token to extract the payload
let decodedToken;
try {
decodedToken = jwt.decode(token, { complete: true }); // Decoding JWT
} catch (err) {
console.error("Error decoding token:", err);
return;
}
if (!decodedToken || !decodedToken.payload) {
console.log("Invalid token payload.");
return;
}
const exp = decodedToken.payload.exp; // Get the expiration time from the payload
console.log(`Token expiration time (exp, in seconds): ${exp}`);
// Check if the token has expired
if (exp > currTime) {
console.log("Token is still valid.");
} else {
console.log("Token has expired. Fetching a new token...");
// Logic to fetch a new token can go here
// After fetching, save the new token back to the cache (record.value)
// Example:
// record.value = fetchNewToken();
}
// Calculate the cache expiration in milliseconds
const cacheExp = exp * 1000;
console.log(`Cache expiration time (in milliseconds): ${cacheExp}`);
// Save the token in the cache until it expires
setTimeout(() => {
console.log("Token expired. Fetching a new token...");
// Fetch new token and store it
// record.value = fetchNewToken();
}, cacheExp - Date.now()); // Subtract current time from cache expiration
console.log("Token will be cached until expiration.");
}
// Example function to fetch a new token (not implemented)
function fetchNewToken() {
// Simulated token fetch logic
const newToken = "new.jwt.token.here";
console.log("New token fetched:", newToken);
return newToken;
}
// Run the token handler function
handleToken();
Imagine you have a web application where users must satisfy certain conditions (password reset, accepting terms, verifying email, etc.) before they can fully access their dashboard. After logging in, the system checks the user’s state and redirects them accordingly.
loc
: The locale or language (e.g., en
, fr
, es
).returnto
: The return URL where the user should go after authentication.token
: The authorization token, which is sent to all redirections to ensure the user remains authenticated.When the user successfully logs in, we’ll check:
/password-change
./legal
./verify-email
./complete-profile
./enable-mfa
.Here’s a Node.js app that demonstrates these conditions:
const express = require("express");
const jwt = require("jsonwebtoken"); // For decoding JWT tokens
const app = express();
const PORT = 3000;
// Mock user data (in real scenarios, you'd fetch this from a database)
const user = {
id: 1,
emailVerified: false,
passwordExpired: true,
termsAccepted: false,
profileComplete: false,
mfaEnabled: false,
};
// Middleware to simulate token and user info retrieval
app.get("/auth/callback", (req, res) => {
const returnTo = req.query.returnto || "/dashboard"; // Default to dashboard if no returnTo provided
const locale = req.query.loc || "en"; // Default to English if no locale is provided
const token = req.query.token || "your-jwt-token-here"; // Authorization token from URL
// Decode the JWT to get the user information (simulated for this example)
const decodedToken = jwt.decode(token);
console.log(`Return URL: ${returnTo}`);
console.log(`Locale: ${locale}`);
console.log(`Authorization Token: ${token}`);
// Conditional redirection logic based on user state
if (user.passwordExpired) {
console.log("Password is expired. Redirecting to password change page.");
return res.redirect(`/password-change?loc=${locale}&token=${token}`);
}
if (!user.termsAccepted) {
console.log("Terms & Conditions not accepted. Redirecting to legal page.");
return res.redirect(`/legal?loc=${locale}&token=${token}`);
}
if (!user.emailVerified) {
console.log("Email not verified. Redirecting to email verification page.");
return res.redirect(`/verify-email?loc=${locale}&token=${token}`);
}
if (!user.profileComplete) {
console.log("Profile incomplete. Redirecting to profile completion page.");
return res.redirect(`/complete-profile?loc=${locale}&token=${token}`);
}
if (!user.mfaEnabled) {
console.log("MFA not enabled. Redirecting to enable MFA page.");
return res.redirect(`/enable-mfa?loc=${locale}&token=${token}`);
}
// If all conditions are met, redirect to the returnTo URL
console.log("All conditions met. Redirecting to:", returnTo);
res.redirect(`${returnTo}?loc=${locale}&token=${token}`);
});
// Example routes for the different redirection flows
app.get("/password-change", (req, res) => {
res.send(`<h1>Password Change</h1><p>Please change your password.</p>`);
});
app.get("/legal", (req, res) => {
res.send(`<h1>Terms & Conditions</h1><p>Please accept the latest terms & conditions.</p>`);
});
app.get("/verify-email", (req, res) => {
res.send(`<h1>Email Verification</h1><p>Please verify your email address.</p>`);
});
app.get("/complete-profile", (req, res) => {
res.send(`<h1>Complete Profile</h1><p>Please complete your profile information.</p>`);
});
app.get("/enable-mfa", (req, res) => {
res.send(`<h1>Enable MFA</h1><p>Please enable Multi-Factor Authentication (MFA).</p>`);
});
// Fallback route for the dashboard
app.get("/dashboard", (req, res) => {
res.send("<h1>Welcome to your dashboard!</h1>");
});
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});