A Deep Dive into HTTP Basic Authentication

By Published On: October 1, 20235.4 min readViews: 214

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:

  1. Simplicity: HTTP Basic Authentication is easy to implement and understand. It requires minimal additional overhead for client and server implementations.
  2. Standardization: It is a standardized authentication method supported by most web browsers and server frameworks.

Cons:

  1. 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.
  2. 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.
  3. Limited Features: It lacks advanced features like multi-factor authentication (MFA) or token-based authentication, which are often needed for more robust security.
  4. No Session Management: Basic Authentication does not manage user sessions. If session management is required, it needs to be implemented separately.
  5. 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).

Share it:

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments