DEV Community

Cover image for HTTP Basic Authentication Explained (RFC 7617)
GeekCoding101.com
GeekCoding101.com

Posted on Originally published at geekcoding101.com

HTTP Basic Authentication Explained (RFC 7617)

Originally published at GeekCoding101.

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.

How HTTP Basic Authentication Works (RFC 7617)

Before writing any code, here is the whole exchange as RFC 7617 defines it:

  1. The client requests a protected resource without credentials.
  2. The server answers 401 Unauthorized with a challenge header that names the scheme and a realm (a label for the protected area):
   HTTP/1.1 401 Unauthorized
   WWW-Authenticate: Basic realm="admin area", charset="UTF-8"
Enter fullscreen mode Exit fullscreen mode
  1. The client joins the user ID and password with a single colon, Base64-encodes the result, and sends it in the Authorization header. For alice and s3cret:
   GET /protected HTTP/1.1
   Host: example.com
   Authorization: Basic YWxpY2U6czNjcmV0
Enter fullscreen mode Exit fullscreen mode
  1. The server decodes the value, splits it at the first colon (so the user ID itself cannot contain a colon, but the password can), and checks the pair. A match returns 200 OK; a mismatch returns 401 again.

You can produce the same header from the command line:

echo -n 'alice:s3cret' | base64          # YWxpY2U6czNjcmV0
curl -u alice:s3cret https://example.com/protected   # curl builds the header for you
Enter fullscreen mode Exit fullscreen mode

A few details from RFC 7617 that tutorials often skip:

  • Base64 is encoding, not encryption. Anyone who sees the header can decode it in one step, so Basic Auth is only acceptable over HTTPS.
  • charset="UTF-8" is the only charset the server may announce, and tells the client to encode non-ASCII user names and passwords as UTF-8.
  • No session, no logout. The browser caches the credentials for the realm and resends them on every request until the browser is closed; there is no standard way for the server to "log out" a user.
  • CSRF exposure. Because the browser attaches the credentials automatically, a forged cross-site request carries them too, so state-changing endpoints still need CSRF protection.

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

 

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}`);
});
Enter fullscreen mode Exit fullscreen mode

 

3. Testing the Server

Launch the server:

npx ts-node ./app.ts
Enter fullscreen mode Exit fullscreen mode

Open another terminal and run below command:

echo '{"username": "testuser01", "password": "testpassword01"}' | http POST http://localhost:3001/register
Enter fullscreen mode Exit fullscreen mode

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.

FAQ

Is HTTP Basic Authentication secure?

Only over HTTPS. The credentials are Base64-encoded, which anyone can reverse, and they are sent with every request. Over TLS the header is encrypted in transit, which makes Basic Auth acceptable for internal tools, staging sites and machine-to-machine calls, but it still has no MFA, no expiry and no logout.

What is the format of the Basic Authorization header?

Authorization: Basic <base64(user-id:password)>, for example Authorization: Basic YWxpY2U6czNjcmV0 for alice:s3cret.

What does the server send when credentials are missing or wrong?

401 Unauthorized with WWW-Authenticate: Basic realm="...". The browser shows its login dialog when it sees this header.

Basic Auth vs Bearer tokens?

Basic sends the user's actual password on every request. A Bearer token (for example an OAuth 2.0 access token or a JWT) is a separate, revocable, usually short-lived credential, so a leaked token does far less damage than a leaked password.

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).

Top comments (0)