simple-express-react-auth
Version:
A lightweight authentication package for Express/React apps with single password protection using cookie-session
119 lines (101 loc) • 3.54 kB
JavaScript
const express = require('express');
const bcrypt = require('bcrypt');
/**
* Creates authentication middleware and routes for Express
* @param {Object} options - Configuration options
* @param {string} options.password - The plain text password to hash and use for authentication
* @param {string} [options.hashedPassword] - Pre-hashed password (alternative to password)
* @param {string} [options.loginPath='/login'] - Login endpoint path (relative to router mount)
* @param {string} [options.logoutPath='/logout'] - Logout endpoint path (relative to router mount)
* @param {string} [options.statusPath='/status'] - Status endpoint path (relative to router mount)
* @param {number} [options.saltRounds=12] - BCrypt salt rounds (increased for better security)
* @returns {Object} Express router and middleware functions
*/
function createAuth(options = {}) {
const {
password,
hashedPassword,
loginPath = '/login',
logoutPath = '/logout',
statusPath = '/status',
saltRounds = 12
} = options;
if (!password && !hashedPassword) {
throw new Error('Either password or hashedPassword must be provided');
}
let bcryptPassword;
// Initialize the password hash
const initPassword = async () => {
if (hashedPassword) {
bcryptPassword = hashedPassword;
} else {
bcryptPassword = await bcrypt.hash(password, saltRounds);
}
};
// Initialize password immediately
initPassword();
const router = express.Router();
// Status endpoint
router.get(statusPath, (req, res) => {
if (req.session && req.session.authenticated) {
res.status(200).json({ status: 'Authenticated', authenticated: true });
} else {
res.status(200).json({ status: 'Unauthenticated', authenticated: false });
}
});
// Login endpoint
router.post(loginPath, async (req, res) => {
const { password: inputPassword } = req.body;
if (!inputPassword) {
return res.status(400).json({ error: 'Password is required', authenticated: false });
}
try {
// Ensure password is initialized
if (!bcryptPassword) {
await initPassword();
}
const result = await bcrypt.compare(inputPassword, bcryptPassword);
if (result) {
req.session.authenticated = true;
res.status(200).json({ message: 'Login successful', authenticated: true });
} else {
res.status(401).json({ error: 'Invalid password', authenticated: false });
}
} catch (err) {
console.error('Login error:', err);
res.status(500).json({ error: 'Internal server error', authenticated: false });
}
});
// Logout endpoint
router.get(logoutPath, (req, res) => {
req.session = null; // Clear the session
res.status(200).json({ message: 'Logged out successfully', authenticated: false });
});
// Middleware to protect routes
const requireAuth = (req, res, next) => {
if (req.session && req.session.authenticated) {
next();
} else {
res.status(401).json({ error: 'Authentication required', authenticated: false });
}
};
// Middleware to redirect unauthenticated users (for HTML responses)
const requireAuthRedirect = (redirectUrl = '/login') => {
return (req, res, next) => {
if (req.session && req.session.authenticated) {
next();
} else {
res.redirect(redirectUrl);
}
};
};
return {
router,
requireAuth,
requireAuthRedirect,
initPassword
};
}
module.exports = {
createAuth
};