Back to Blog
Lesson 26 of the Cloudflare: Cloudflare for Developers: DNS to CDN course
Cloud NativeAugust 3, 20263 min read

Authentication Fundamentals: Securing Your Cloudflare Workers API

Learn to build secure authentication for your API using Cloudflare Workers. We cover password hashing, header validation, and implementing a robust login flow.

AuthSecurityAPIAuthenticationWorkers
Close-up of a computer screen displaying an authentication failed message.

Previously in this course, we built The Dynamic Backend to store asset metadata in D1. Now, we need to ensure that only authorized users can interact with our sensitive API routes.

Authentication is the process of verifying who a user is. In a serverless environment like Cloudflare Workers, we don't have persistent sessions in the traditional sense, so we rely on stateless verification.

Principles of Secure Authentication

When building custom authentication for an API, you must adhere to three core pillars:

  1. Never store plain-text passwords: Always use a slow, salted cryptographic hash function (like bcrypt or Argon2).
  2. Never send credentials in the URL: Credentials must be sent in the request body (during login) or headers (during subsequent requests).
  3. Use secure transmission: Always enforce HTTPS. If you followed our Configuring SSL/TLS Settings lesson, your origin is already protected.

Storing Hashed Passwords

We will use the Web Crypto API, which is built into the Cloudflare Workers runtime, to handle hashing. We never store the actual password; we store the result of a one-way function.

Here is how you generate a hash for a password using SHA-256 (for production, consider bcrypt via a library if you need salt handling, but this illustrates the primitive):

JAVASCRIPT
async function hashPassword(password) {
  const encoder = new TextEncoder();
  const data = encoder.encode(password);
  const hash = await crypto.subtle.digest(CE9178">'SHA-256', data);
  return Array.from(new Uint8Array(hash))
    .map(b => b.toString(16).padStart(2, CE9178">'0'))
    .join(CE9178">'');
}

Implementing the Login Flow

Our login flow consists of two steps:

  1. Verification: The user sends a POST request with their email and password. We query D1 to find the user and compare the provided password hash against the stored hash.
  2. Authorization: If they match, we return a unique token (typically a JWT). For this beginner level, we will use a simple custom header token system to validate requests.

Example: Validating a Request

We’ll create a middleware-style function that checks for an X-API-Key header.

JAVASCRIPT
export default {
  async fetch(request, env) {
    const apiKey = request.headers.get("X-API-Key");

    // Simple validation against an environment variable
    if (apiKey !== env.SECRET_API_TOKEN) {
      return new Response("Unauthorized", { status: 401 });
    }

    return new Response("Welcome to the secure zone!");
  }
}

Hands-on Exercise

  1. Update your D1 schema: Add a users table to your D1 database with columns for email and password_hash.
  2. Create a Registration Worker: Write a POST endpoint that accepts an email/password, hashes the password using the hashPassword function above, and inserts it into D1.
  3. Implement the Header Check: Add the snippet above to your existing API routes to protect them from unauthorized access.

Common Pitfalls

  • Timing Attacks: Comparing hashes with a standard == operator can be vulnerable to timing attacks. Always use a constant-time comparison function when checking sensitive strings.
  • Logging Credentials: Never console.log the request body or headers during authentication. This will leak credentials into your Cloudflare dashboard logs.
  • Storing Secrets in Code: Never hardcode your API tokens. We will cover how to manage these securely in a future lesson using wrangler secret.

Frequently Asked Questions

Q: Why not use simple string comparison for passwords? A: If your database is ever leaked, an attacker would have every user's password instantly. Hashing ensures that even with the database file, they cannot recover the original passwords.

Q: Is SHA-256 enough for passwords? A: It is a fast hash. For high-security systems, use a "key derivation function" like Argon2 or bcrypt, which are designed to be slow, making brute-force attacks significantly more expensive for attackers.

Q: What if I forget to set the header? A: The server will return a 401 Unauthorized. This is the correct behavior for any API that requires authentication.

Recap

We’ve learned that security starts with never trusting the client, hashing sensitive data, and using headers for stateless validation. By integrating these practices into our Workers, we ensure our API remains a secure, private resource.

Up next: Rate Limiting Basics — we'll look at how to protect these endpoints from brute-force login attempts.

Similar Posts