Skip to main content
Authentication Systems Tutorial
CHAPTER 11 Intermediate

Authentication in REST APIs

Updated: May 14, 2026
30 min read

# CHAPTER 11

Authentication in REST APIs

1. Introduction

We have thoroughly covered how human users authenticate via web browsers and mobile apps using JWTs and Social Logins. But what happens when another *machine* needs to access your database? If you build a SaaS product, your corporate clients will want to write Python scripts that automatically pull their billing data from your system every midnight. Scripts cannot fill out HTML login forms or click "Log in with Google." In this chapter, we will explore Machine-to-Machine (M2M) Authentication, focusing on API Keys, Bearer Tokens, and securing headless REST endpoints.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Distinguish between User Authentication and Machine Authentication.
  • Understand the concept and implementation of API Keys.
  • Securely generate, store, and validate long-lived API Keys.
  • Compare API Keys to the OAuth 2.0 Client Credentials Flow.
  • Implement rate limiting to protect public APIs.

3. Beginner-Friendly Explanation

Imagine a VIP Bank Vault.
  • Human Authentication (JWT): A customer walks in, shows their ID to the teller, gets verified, and is given a temporary visitor badge (JWT) that expires in 15 minutes.
  • Machine Authentication (API Key): The bank hires an automated armored truck company to drop off cash every night at 2:00 AM. There are no tellers working at 2:00 AM. Instead, the bank gives the armored truck driver a special, permanent, heavy-duty key (An API Key). The driver simply inserts the key into the loading dock door. As long as the key is valid, the door opens.

4. What is an API Key?

An API Key is a long, randomly generated string of characters (e.g., sk_live_51Hxyz...) provided to a developer when they register for your service.

Characteristics of API Keys:

  1. 1. Long-Lived: Unlike JWTs, they rarely expire automatically. They exist until the developer manually revokes them.
  1. 2. Opaque: Unlike JWTs, they are not Base64 encoded JSON. They are meaningless random strings. The server MUST query the database to see which developer owns the key.
  1. 3. Stateless Transmission: Like JWTs, they must be transmitted with every single HTTP request.

5. Implementing API Keys

When a developer generates an API key in your dashboard, you must store it securely. Because API keys grant massive access, they must be treated exactly like passwords.

Database Schema:

sql
123456
CREATE TABLE api_keys (
    id INT PRIMARY KEY,
    user_id INT, -- The developer who owns it
    key_hash VARCHAR(255), -- NEVER store the raw key!
    created_at TIMESTAMP
);

Generation Logic (Node.js/Crypto):

javascript
1234567891011121314
const crypto = require('crypto');
const bcrypt = require('bcrypt');

// 1. Generate the raw, 32-byte API Key
const rawApiKey = crypto.randomBytes(32).toString('hex');
const prefix = "sk_live_"; // Standard prefix (like Stripe)
const finalApiKey = prefix + rawApiKey;

// 2. Hash it before saving to the database
const hashedKey = await bcrypt.hash(finalApiKey, 10);
await database.save(hashedKey);

// 3. Show it to the developer EXACTLY ONCE
console.log(`Your API Key is: ${finalApiKey}. Store it safely, you will never see it again.`);

6. Verifying API Keys (Middleware)

When the developer's script makes a request, it sends the key in the headers.
Express.js
1234567891011121314151617181920212223242526
async function verifyApiKey(req, res, next) {
    // 1. Extract the key from the custom header or Authorization header
    const apiKey = req.headers['x-api-key'] || req.headers.authorization?.split(' ')[1];

    if (!apiKey) {
        return res.status(401).json({ error: "Missing API Key" });
    }

    // 2. Look up the hash in the database
    // (In reality, you need an index or a clever prefixing strategy to find it fast)
    const storedHash = await database.findHashForUser(req.body.userId); 
    
    // 3. Verify mathematically
    const isValid = await bcrypt.compare(apiKey, storedHash);
    
    if (!isValid) {
        return res.status(403).json({ error: "Invalid API Key" });
    }
    
    next();
}

// Protecting the Route
app.get('/api/billing-data', verifyApiKey, (req, res) => {
    res.json({ data: "Sensitive Billing Info" });
});

7. OAuth Client Credentials Flow (Advanced)

API Keys are great for simple scripts. However, for massive enterprise systems (like two backend servers talking to each other securely), the industry standard is the OAuth 2.0 Client Credentials Grant.

Instead of a single, permanent API key, the machine is given a Client ID and a Client Secret. The machine sends a POST request to an Authorization Server with these credentials. If valid, the server returns a temporary Access Token (JWT). The machine uses that temporary token to hit the API. This is significantly more secure because the actual token expires quickly, limiting the damage if it is intercepted.

8. Backend Workflow: Rate Limiting

Because scripts can execute thousands of requests per second, a broken script with a valid API key can accidentally DDoS (Distributed Denial of Service) your server, crashing it for everyone. You MUST implement Rate Limiting on all API routes. For example, limiting a specific API Key to 100 requests per minute. If they exceed it, the server returns HTTP status 429 Too Many Requests.

9. Best Practices

  • Key Prefixing: Look at Stripe's API keys (e.g., sk_live_abc123). The sk stands for Secret Key, and live means it's a production key. Prefixing your keys makes it incredibly easy for developers to distinguish between their test keys (sk_test_) and live keys, preventing accidental data destruction. Furthermore, GitHub automated scanners look for these prefixes and will alert you if you accidentally commit a live key to a public repository!

10. Common Mistakes

  • Passing Keys in the URL: Never instruct developers to pass API keys in the query string (e.g., api.com/data?api_key=secret123). URLs are logged in plain text by intermediate proxy servers, internet service providers, and browser histories. API keys must ALWAYS be transmitted securely in HTTP Headers over an HTTPS connection.

11. Exercises

  1. 1. Explain why a backend system should treat the storage of generated API Keys exactly like user passwords (i.e., using hashing algorithms). What is the security risk if they are stored in plain text?

12. Coding Challenges

  • Challenge: If you are using Express.js, research the express-rate-limit package. Write a conceptual snippet of code demonstrating how you would apply a global rate limiter of 50 requests per 15 minutes to all routes starting with /api/.

13. MCQs with Answers

Question 1

When designing Machine-to-Machine (M2M) API authentication, what is the primary structural difference between an API Key and a JSON Web Token (JWT)?

Question 2

To prevent a developer's automated script from accidentally overwhelming your backend server with thousands of requests per second, what architectural defense must be implemented alongside API Key authentication?

14. Interview Questions

  • Q: Compare and contrast simple API Keys with the OAuth 2.0 Client Credentials Flow for Machine-to-Machine communication. In what enterprise scenario would the OAuth flow be mandated over static API keys?
  • Q: Explain why API keys must be transmitted via HTTP Headers rather than URL query parameters, and why HTTPS encryption is absolutely mandatory for endpoints accepting API keys.

15. FAQs

Q: How do I easily find a hashed API key in the database if I only have the raw key from the request? A: This is a classic engineering problem! Because bcrypt generates a different hash every time, you can't just SELECT * WHERE hash = ?. The solution is a "Split Key" architecture. You generate the key in two parts: Identifier.Secret (e.g., ID888.Secret999). You store the Identifier in plain text in the DB, and you store the *hash* of the Secret. When the request comes in, you find the user instantly using ID888, and then bcrypt.compare() the Secret999 part against the hash!

16. Summary

In Chapter 11, we explored Machine-to-Machine (M2M) authentication. We learned that automated scripts cannot interact with human login forms, necessitating the use of long-lived, opaque API Keys. We examined the critical security requirements for managing these keys, including cryptographic generation, hashed database storage, secure header transmission, and "Show Once" frontend logic. Finally, we emphasized the necessity of Rate Limiting to protect backend infrastructure from aggressive automated scripts.

17. Next Chapter Recommendation

Our machines are secure, but humans are prone to having their passwords stolen. How do we protect user accounts when their password has been compromised? Proceed to Chapter 12: Multi-Factor Authentication (MFA).

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·