Implementing Production-Grade JWT Authentication with Express and TypeScript
These articles are AI-generated summaries. Please check the original sources for full details.
JWT Auth in Express with TS
The NHero authentication system implements a dual-token architecture using JSON Web Tokens (JWT). It utilizes short-lived access tokens paired with database-stored refresh tokens to balance security and user experience.
Why This Matters
Many beginner implementations rely on a single JWT stored in LocalStorage, leaving applications vulnerable to XSS attacks and making session invalidation impossible. A production-ready model requires stateful refresh token management and HTTP-only cookies to ensure that sensitive credentials cannot be accessed by client-side scripts.
Key Insights
- Token Rotation: Using Access Tokens for short-term auth and Refresh Tokens for new access token generation prevents long-term session hijacking.
- Secure Storage: Implementing ‘httpOnly: true’ cookies protects tokens from XSS attacks compared to LocalStorage.
- Schema Integration: Attaching methods like ‘isPasswordCorrect’ directly to Mongoose schemas ensures logic stays close to the model for better reusability.
- Stateful Revocation: Storing refresh tokens in the database allows for proper logout functionality and detection of token reuse attacks.
Working Examples
Mongoose pre-save middleware for automatic password hashing.
userSchema.pre("save", async function (): Promise<void> {
if (!this.isModified("password")) return;
this.password = await bcrypt.hash(this.password, 10);
});
Middleware to verify JWTs and validate user existence in the database.
export const verifyJWT = async (req: Request, _: Response, next: NextFunction) => {
try {
const accessToken = req.cookies?.accessToken || req.header("Authorization")?.replace("Bearer ", "");
if (!accessToken) {
throw new ApiError(401, "Access token is missing");
}
const decodedToken = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET!!) as JwtPayload;
const user = await User.findById(decodedToken._id).select("-password -refreshToken");
if (!user) {
throw new ApiError(401, "Invalid Access Token");
}
req.user = user;
next();
} catch (error: any) {
throw new ApiError(401, error?.message || "Invalid Access Token");
}
};
Practical Applications
- …Use Case: Session Management; behavior involves storing refresh tokens in MongoDB to allow remote session invalidation during logout.
- …Pitfall: Sending sensitive fields; returning hashed passwords or refresh tokens in API responses exposes the system to unnecessary risk.
References:
Continue reading
Next article
The Shift to Distributed Tracing: How OpenTelemetry Standardized Observability
Related Content
Backend Security in the AI Era: Why 'It Boots' Is Not Enough
DaloyJS 1.0.0-beta.0 launches with secure defaults to counter AI-generated backend code vulnerabilities.
Node.js Secret Management: Implementing Vault, AWS Secrets Manager, and Zero-Leakage Patterns
Secure Node.js production environments using AWS Secrets Manager and HashiCorp Vault to eliminate plaintext .env vulnerabilities and implement automated secret rotation.
Implementing Persistent JWT Signing Keys with PostgreSQL and Envelope Encryption
Secure your OIDC server by replacing in-memory JWKS with an encrypted PostgreSQL store using AES-256-GCM envelope encryption for persistent token verification.