Express Basic Auth Middleware
express-basic-auth is a lightweight, plug-and-play middleware designed for adding HTTP Basic Authentication to Express applications. Its current stable version is 1.2.1, with the latest release in October 2021. The package has seen sporadic updates, with v1.0.0 (production ready) in 2017 and v1.1.0 (TypeScript support) in 2020. It offers flexibility through static user configurations or custom synchronous/asynchronous authorizer functions. A key differentiator is the inclusion of a `safeCompare` utility, which aids in mitigating timing attacks for secure credential comparison. The middleware also allows customization of unauthorized responses, including JSON, and exposes parsed credentials on `req.auth`.
Common errors
-
TypeError: (0 , express_basic_auth_1.default) is not a function
cause Attempting to destructure the default export of `express-basic-auth` as a named export in an ESM context, or incorrect `require` syntax in CommonJS.fixFor ESM, change `import { basicAuth } from 'express-basic-auth'` to `import basicAuth from 'express-basic-auth'`. For CommonJS, ensure `const basicAuth = require('express-basic-auth')` is used. -
TS2345: Argument of type '{ users: { admin: string; }; }' is not assignable to parameter of type 'IOptions'.cause This error typically occurs when using `express-basic-auth` v1.1.0 with TypeScript, as that version contained faulty type declarations.fixUpgrade `express-basic-auth` to version 1.1.1 or newer to resolve the TypeScript declaration issues. Run `npm install express-basic-auth@latest`. -
Basic authentication fails silently, or unauthorized requests are not challenged.
cause This can happen due to incorrect comparison logic in a custom `authorizer` function, or if the `challenge` option is set to `false` (which is not the default, but possible to override) without providing an `unauthorizedResponse`.fixCarefully review your `authorizer` function to ensure it returns `true` or `false` correctly, utilizing `basicAuth.safeCompare` for security. Ensure the `challenge` option is `true` if you expect the browser to prompt for credentials, and consider setting a `realm` for a clearer user experience (e.g., `{ challenge: true, realm: 'Restricted Area' }`).
Warnings
- breaking TypeScript declarations in version 1.1.0 contain a known issue that can lead to compilation errors or incorrect type inference.
- gotcha Custom authorizer functions, if not implemented carefully, can introduce timing vulnerabilities, potentially exposing secret credentials through variations in response times.
- gotcha The package's primary export is the middleware function itself, which can lead to incorrect import statements, especially when mixing CommonJS and ESM modules.
Install
-
npm install express-basic-auth -
yarn add express-basic-auth -
pnpm add express-basic-auth
Imports
- basicAuth
import { basicAuth } from 'express-basic-auth';import basicAuth from 'express-basic-auth';
- safeCompare
import { safeCompare } from 'express-basic-auth';import basicAuth from 'express-basic-auth'; // then use basicAuth.safeCompare
- IOptions
import { IOptions } from 'express-basic-auth';import type { IOptions } from 'express-basic-auth';
Quickstart
import express from 'express';
import basicAuth from 'express-basic-auth';
import type { IOptions } from 'express-basic-auth'; // Import the type for better DX
const app = express();
const port = 3000;
// Example 1: Static users
app.use('/admin-static', basicAuth({
users: { 'admin': 'supersecret', 'editor': 'editpass' },
challenge: true, // Always challenge if credentials are not provided or incorrect
realm: 'Static Admin Area' // Custom realm
}));
// Example 2: Custom asynchronous authorizer
const myAsyncAuthorizer: IOptions['authorizer'] = (username, password, cb) => {
// In a real app, you'd fetch from a DB, check hashed passwords, etc.
// Simulate async operation
setTimeout(() => {
const userMatches = basicAuth.safeCompare(username, 'customuser');
const passwordMatches = basicAuth.safeCompare(password, 'custompassword');
const authorized = userMatches && passwordMatches; // Use logical for final decision, bitwise for comparisons
if (authorized) {
cb(null, true); // No error, authorized
} else {
cb(null, false); // No error, not authorized
}
}, 100);
};
app.use('/admin-custom', basicAuth({
authorizer: myAsyncAuthorizer,
authorizeAsync: true, // Crucial for async authorizers
challenge: true,
realm: 'Custom Auth Area',
unauthorizedResponse: (req) => {
return req.auth ?
('Credentials ' + req.auth.user + ':' + req.auth.password + ' rejected.') :
'No credentials provided';
}
}));
// Route for static users
app.get('/admin-static', (req, res) => {
res.send(`Hello, ${req.auth?.user}! Welcome to the static admin area.`);
});
// Route for custom authorizer
app.get('/admin-custom', (req, res) => {
res.send(`Hello, ${req.auth?.user}! Welcome to the custom authorized area.`);
});
// Public route
app.get('/', (req, res) => {
res.send('This is a public page.');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
console.log('Try accessing:');
console.log(` - http://localhost:${port}/admin-static (user: admin, pass: supersecret)`);
console.log(` - http://localhost:${port}/admin-custom (user: customuser, pass: custompassword)`);
console.log(` - http://localhost:${port}/`);
});