Introduction
In this blog post, we will dive into HTTP Basic Authentication, a method rooted in the principles outlined in RFC 7617.
It’s worth noting that, the RFC specification defines the use of the “Authorization” header in HTTP requests to transmit the credentials. The credentials are typically sent as a Base64-encoded string of the form username:password
. It also describes how servers should respond with appropriate status codes (e.g., 401 Unauthorized) when authentication fails.
Step 1: Setting Up the Node.js and TypeScript Environment
Please refer to the steps explained in our previous blog post Password Authentication In Node.Js: A Step-By-Step Guide at Step 1: Setting Up the Node.js and TypeScript Environment.
Step 2: Creating the Server
usersData.ts
In this file, we define a simulated database of users with their hashed passwords using bcrypt. Each user has a username
and a password
field.
This file acts as our database for the sake of this example.
The usage of bcrypt also has been explained in Password Authentication In Node.Js: A Step-By-Step Guide already.
interface User { username: string; password: string; } const users: User[] = []; export default users;
basicAuthMiddleware.ts
This file contains the basic authentication middleware. The middleware is responsible for authenticating users based on the credentials provided in the Authorization
header. It uses bcrypt to compare the provided password with the hashed password stored in the usersData.ts
file.
import { Request, Response, NextFunction } from 'express'; import { Buffer } from 'buffer'; import bcrypt from 'bcryptjs'; interface User { username: string; password: string; } const basicAuthMiddleware = (users: User[]) => async (req: Request, res: Response, next: NextFunction) => { try { const authHeader = req.headers.authorization; if (!authHeader) { // If no authorization header is provided, send a 401 response with the WWW-Authenticate header // so that browser will pop up username/password dialog res.setHeader('WWW-Authenticate', 'Basic'); return res.status(401).json({ error: 'Authorization header missing' }); } const credentials = Buffer.from(authHeader.split(' ')[1], 'base64').toString('utf-8'); const [username, password] = credentials.split(':'); const user = users.find((user) => user.username === username); if (!user) { return res.status(401).json({ error: 'Invalid username' }); } const isPasswordValid = await bcrypt.compare(password, user.password); if (!isPasswordValid) { return res.status(401).json({ error: 'Invalid password' }); } next(); } catch (error) { res.status(500).json({ error: 'Internal server error' }); } }; export default basicAuthMiddleware;
In the interest of security, a production-ready authentication system should not provide explicit feedback on whether the username or password is invalid. However, the code examples provided in this article aim to illustrate the principles of Basic Authentication based on RFC 7617 and are intended for educational purposes. They demonstrate the basic mechanics of authentication but may not fully address all security concerns.
app.ts
The app.ts
file will set up the Express server, handles user registration, and protects a route using the basic authentication middleware.
By implementing authentication in middleware, when the middleware detects invalid credentials, it directly sends the appropriate error response, and the route handler will not be executed.
app.ts
imports the users' data from usersData.ts
and creates the middleware by passing the users' data as an argument to basicAuthMiddleware
.
The /register
will take {"username": "your_name", "password": "your_password"}
as input.
The /protected
endpoint is to verify account credentials.
import express from 'express'; import bodyParser from 'body-parser'; import bcrypt from 'bcryptjs'; import basicAuthMiddleware from './basicAuthMiddleware'; // Import the basicAuthMiddleware import users from './usersData'; // Import the users data const app = express(); const PORT = 3001; app.use(bodyParser.json()); // User registration route app.post('/register', async (req, res) => { try { const { username, password } = req.body; // Check if the user already exists if (users.some((user) => user.username === username)) { return res.status(400).json({ error: 'Username already exists' }); } // Hash the password using bcrypt const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(password, salt); // Save the user in the database (in this example, we're using an in-memory array) const newUser = { username, password: hashedPassword }; users.push(newUser); res.status(201).json({ message: 'User registered successfully!' }); } catch (error) { res.status(500).json({ error: 'Internal server error' }); } }); // Create the basicAuthMiddleware with the users array as an argument const authMiddleware = basicAuthMiddleware(users); // Use the authMiddleware to protect a route app.get('/protected', authMiddleware, (req, res) => { res.json({ message: 'You have successfully accessed the protected route!' }); }); app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });
3. Testing the Server
Launch the server:
npx ts-node ./app.ts
Open another terminal and run below command:
echo '{"username": "testuser01", "password": "testpassword01"}' | http POST http://localhost:3001/register
It created a user testuser01
in the server with password testpassword01
.
Let’s try to access the protected URI /protected
:
Goto online Base64 encode/decode website (link here), we can see the decoding results:
If trying to access from browser, the browser will pop up the username/password verification dialog automatically as below:
Pros and Cons
Pros:
- Simplicity: HTTP Basic Authentication is easy to implement and understand. It requires minimal additional overhead for client and server implementations.
- Standardization: It is a standardized authentication method supported by most web browsers and server frameworks.
Cons:
- Security: The credentials are Base64-encoded but not encrypted. This means they can be intercepted if transmitted over an insecure network. It’s crucial to use HTTPS to mitigate this issue.
- No Built-in Password Hashing: Basic Authentication does not provide built-in mechanisms for securely storing or hashing passwords. Implementing password hashing and salting is the responsibility of the application developer. Like in our article, we have to implement password hashing.
- Limited Features: It lacks advanced features like multi-factor authentication (MFA) or token-based authentication, which are often needed for more robust security.
- No Session Management: Basic Authentication does not manage user sessions. If session management is required, it needs to be implemented separately.
- User Experience: While browsers handle the credential prompt, the user experience can be intrusive, especially for web applications.
Summary
HTTP Basic Authentication is a straightforward method for securing web resources. It serves well for simple use cases but may not be suitable for applications requiring more advanced security measures.
The source code of this tutorial has been uploaded to GeekCoding101 github repo as well, feel free to take a look.
In the next blog, we will explore more advanced authentication methods, including token-based authentication using JSON Web Tokens (JWT).