DEV Community

Nandalal Shukla
Nandalal Shukla

Posted on

Creating production-ready authentication and authorization

Creating production-ready authentication and authorization requires robust security practices. Below is a complete, modular setup for an Express application using JWT, HttpOnly cookies, and role-based access control.
Prerequisites
Project Setup:
bash
mkdir express-auth-api
cd express-auth-api
npm init -y
npm install express jsonwebtoken cookie-parser dotenv
Use code with caution.

Environment File: Create a .env file in your project root:
env

.env

JWT_SECRET=YOUR_VERY_STRONG_RANDOM_SECRET_KEY_HERE
NODE_ENV=development
Use code with caution.

The Production-Ready Code
This solution is broken down into four files:
server.js: The main application file.
middleware/auth.js: The authentication middleware.
middleware/roleAuth.js: The authorization middleware.
routes/authRoutes.js: Separate routes for login/logout (good practice for larger apps).

  1. server.js (Main Application File) This file sets up the server, connects middleware, and defines protected routes. javascript // server.js const express = require('express'); const cookieParser = require('cookie-parser'); const dotenv = require('dotenv'); const authRoutes = require('./routes/authRoutes'); const authenticateToken = require('./middleware/auth'); const authorizeRole = require('./middleware/roleAuth');

dotenv.config();

const app = express();
const PORT = process.env.PORT || 3000;
const isProduction = process.env.NODE_ENV === 'production';

// --- Middleware Setup ---
app.use(express.json()); // Allows parsing JSON bodies from POST requests
app.use(cookieParser()); // Allows parsing cookies from incoming requests

// --- Routes ---

// Use separate route files for authentication (login/logout)
app.use('/api/auth', authRoutes);

// Example Public Route
app.get('/api/public', (req, res) => {
res.status(200).json({ message: 'This is a public endpoint.' });
});

// --- Protected Routes (Examples) ---

// Route accessible by all authenticated users
app.get('/api/profile', authenticateToken, (req, res) => {
// req.user contains { id, username, role } thanks to authenticateToken
res.status(200).json({
message: Welcome ${req.user.username}, you are authenticated!,
user: req.user
});
});

// Route accessible only by 'admin' users
app.get('/api/admin-dashboard', authenticateToken, authorizeRole('admin'), (req, res) => {
res.status(200).json({
message: 'Welcome to the Admin Dashboard, only visible to Admins!'
});
});

// Route accessible only by 'user' role
app.get('/api/user-dashboard', authenticateToken, authorizeRole('user'), (req, res) => {
res.status(200).json({
message: 'Welcome to the User Dashboard.'
});
});

// --- Server Start ---
app.listen(PORT, () => {
console.log(Server running on http://localhost:${PORT});
});
Use code with caution.

  1. middleware/auth.js (Authentication Middleware) This middleware verifies the JWT and attaches the decoded user data to the request object. javascript // middleware/auth.js const jwt = require('jsonwebtoken');

function authenticateToken(req, res, next) {
// 1. Extract the token from the HttpOnly cookie
const token = req.cookies && req.cookies.accessToken;

if (token == null) {
    // If no token exists in the cookie, the user is not authenticated.
    return res.status(401).json({ message: 'Authentication required. No token provided.' });
}

// 2. Verify the token's signature using the secret key
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
        // If the token is invalid (expired, manipulated, etc.)
        return res.status(403).json({ message: 'Forbidden. Invalid or expired token.' });
    }

    // 3. If valid, attach the decoded user payload ({ id, username, role }) to the request
    req.user = user;

    // 4. Pass control to the next middleware or route handler
    next();
});
Enter fullscreen mode Exit fullscreen mode

}

module.exports = authenticateToken;
Use code with caution.

  1. middleware/roleAuth.js (Authorization Middleware) This middleware uses the req.user data provided by authenticateToken to check for specific roles. javascript // middleware/roleAuth.js

/**

  • Factory function to create authorization middleware for a specific required role.
  • @param {string} requiredRole - The role needed for access (e.g., 'admin', 'user')
    */
    function authorizeRole(requiredRole) {
    return (req, res, next) => {
    // We assume req.user has been populated by the authenticateToken middleware
    if (!req.user || req.user.role !== requiredRole) {
    return res.status(403).json({
    message: Access denied. You do not have the required role: ${requiredRole}.
    });
    }

    // Role matches, proceed to the next handler
    next();
    

    };
    }

module.exports = authorizeRole;
Use code with caution.

  1. routes/authRoutes.js (Login/Logout Logic) Separating auth logic keeps your main server file clean. javascript // routes/authRoutes.js const express = require('express'); const jwt = require('jsonwebtoken'); const router = express.Router();

// Mock database of users (replace with a real DB query in production)
const mockUsersDb = [
{ id: 1, username: 'user', role: 'user', passwordHash: 'password123' },
{ id: 2, username: 'admin', role: 'admin', passwordHash: 'adminpass' }
];

const isProduction = process.env.NODE_ENV === 'production';

// --- Login Route ---
router.post('/login', (req, res) => {
const { username, password } = req.body;
const user = mockUsersDb.find(u => u.username === username);

// In a production app, use bcrypt to compare password hashes
if (!user || user.passwordHash !== password) {
    return res.status(401).json({ message: 'Invalid credentials' });
}

// Payload includes necessary claims, crucially the 'role'
const payload = { id: user.id, username: user.username, role: user.role };
const accessToken = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' });

// Set the HttpOnly cookie for secure storage
res.cookie('accessToken', accessToken, {
    httpOnly: true, // Prevents client-side JS access (XSS mitigation)
    secure: isProduction, // Ensures cookie is sent only over HTTPS in production
    sameSite: 'Strict', // Mitigates CSRF
    maxAge: 3600000 // 1 hour expiration
});

res.status(200).json({ message: 'Login successful', role: user.role });
Enter fullscreen mode Exit fullscreen mode

});

// --- Logout Route ---
router.post('/logout', (req, res) => {
res.clearCookie('accessToken', {
httpOnly: true,
secure: isProduction,
sameSite: 'Strict',
});
res.status(200).json({ message: 'Logged out successfully' });
});

module.exports = router;
Use code with caution.

Top comments (0)