Open 1varunvc opened 2 days ago
Securing user authentication tokens is crucial to protect your users and application from potential security threats like token theft or session hijacking. It is a guide through configuring secure storage of user authentication tokens in your Node.js app using express-session
, and explain how to prevent exposure to client-side scripts using HttpOnly cookies.
express-session
There are two common approaches to storing authentication tokens:
express-session
): Tokens are stored on the server in a session store. A session ID is sent to the client in a cookie, which references the session data on the server.For enhanced security, especially when dealing with access tokens and refresh tokens, it's recommended to use server-side sessions to prevent tokens from being exposed to client-side scripts.
express-session
Since you're already using express-session
in your application, we'll focus on securing it properly.
Ensure you have express-session
installed:
npm install express-session
express-session
with Secure OptionsIn your server.js
(or main application file), configure express-session
with security best practices:
const session = require('express-session');
const MongoStore = require('connect-mongo'); // For session storage in MongoDB (optional but recommended)
// ...
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: MongoStore.create({ mongoUrl: process.env.MONGODB_URI }), // Use MongoDB to store sessions
cookie: {
secure: process.env.NODE_ENV === 'production', // Set to true in production (requires HTTPS)
httpOnly: true, // Prevents client-side JavaScript from accessing the cookie
sameSite: 'lax', // Helps protect against CSRF attacks
maxAge: 24 * 60 * 60 * 1000, // Session expiration in milliseconds (e.g., 1 day)
},
})
);
Explanation:
secret
: A strong secret key stored in environment variables.store
: Using a session store like MongoDB to persist sessions.cookie.secure
: Ensures cookies are only sent over HTTPS (set to true
in production).cookie.httpOnly
: Prevents client-side scripts from accessing the session cookie.cookie.sameSite
: Helps mitigate CSRF attacks by controlling when cookies are sent.cookie.maxAge
: Specifies when the session cookie should expire.Modify your authentication strategy to store tokens securely in the session.
In your auth.js
file, after successful authentication, store the tokens in the user session without exposing them to the client.
passport.use(
new SpotifyStrategy(
{
clientID: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
callbackURL: process.env.SPOTIFY_REDIRECT_URI,
},
(accessToken, refreshToken, expires_in, profile, done) => {
const user = {
spotifyId: profile.id,
displayName: profile.displayName,
photos: profile.photos,
// Do not include tokens here; store them separately
};
// Store tokens in session
return done(null, { user, accessToken, refreshToken, expiresIn: Date.now() + expires_in * 1000 });
}
)
);
Modify the serializeUser
and deserializeUser
functions to handle the tokens separately.
passport.serializeUser((sessionData, done) => {
// Store necessary user data and tokens in the session
done(null, sessionData);
});
passport.deserializeUser((sessionData, done) => {
// Retrieve the user data and tokens from the session
done(null, sessionData);
});
Explanation:
sessionData
: Contains both user information and tokens.Ensure that tokens are not sent to the client in responses or accessible through client-side code.
In your routes, avoid sending tokens in JSON responses.
// In your protected routes (e.g., /dashboard)
router.get('/dashboard', ensureAuthenticated, (req, res) => {
// Send only necessary user information
res.json({
message: 'Welcome to your dashboard',
user: req.user.user, // Only include user data, exclude tokens
});
});
When making API calls to Spotify, use the tokens stored in the session on the server-side.
// In spotifyRoutes.js
router.get('/api/spotify-data', ensureAuthenticated, async (req, res) => {
try {
// Use tokens from session (req.user.accessToken)
// ...
} catch (error) {
// Handle errors
}
});
For production environments, it's recommended to use a session store to persist sessions. You can use MongoDB with connect-mongo
.
Install connect-mongo
:
npm install connect-mongo
Configure Session Store:
const MongoStore = require('connect-mongo');
app.use(
session({
// ... other options
store: MongoStore.create({ mongoUrl: process.env.MONGODB_URI }),
})
);
Ensure your cookie settings are configured to enhance security.
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'lax',
maxAge: 24 * 60 * 60 * 1000,
}
secure
: Set to true
in production to enforce HTTPS.httpOnly
: Prevents access to the cookie via client-side scripts.sameSite
: Helps prevent CSRF attacks.Add Helmet to set secure HTTP headers.
npm install helmet
const helmet = require('helmet');
app.use(helmet());
If you prefer to use JWTs, you need to handle them securely to prevent exposure.
npm install jsonwebtoken cookie-parser
Issue JWTs on successful authentication and send them as HttpOnly cookies.
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');
app.use(cookieParser());
// In your authentication callback
router.get(
'/auth/spotify/callback',
passport.authenticate('spotify', { failureRedirect: '/login' }),
(req, res) => {
// Generate JWT
const tokenPayload = {
spotifyId: req.user.spotifyId,
// Include other necessary user info
};
const token = jwt.sign(tokenPayload, process.env.JWT_SECRET, { expiresIn: '1h' });
// Set token as HttpOnly cookie
res.cookie('jwt', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
});
res.redirect('/dashboard');
}
);
Create middleware to verify JWTs.
function authenticateJWT(req, res, next) {
const token = req.cookies.jwt;
if (token) {
jwt.verify(token, process.env.JWT_SECRET, (err, userData) => {
if (err) {
return res.sendStatus(403);
}
req.user = userData;
next();
});
} else {
res.sendStatus(401);
}
}
Use the middleware in your routes:
router.get('/dashboard', authenticateJWT, (req, res) => {
res.json({
message: 'Welcome to your dashboard',
user: req.user,
});
});
Note: When using JWTs, avoid storing sensitive tokens (like access tokens or refresh tokens) in the JWT payload.
cookie.secure
to true
to enforce HTTPS.SESSION_SECRET
and JWT_SECRET
in environment variables.sameSite
cookies to prevent cross-site request forgery.By configuring your Node.js application to store authentication tokens securely in server-side sessions using express-session
, and ensuring that cookies are set with httpOnly
and secure
flags, you significantly reduce the risk of token exposure to client-side scripts or malicious actors.
Using server-side sessions keeps sensitive tokens away from the client's browser, and configuring cookies properly ensures that session identifiers are protected. If you choose to use JWTs, storing them in HttpOnly cookies and avoiding including sensitive information in the token payload is crucial.