Skip to content

rownd/cloudflare

Repository files navigation

@rownd/cloudflare

Blazingly fast authentication for Cloudflare Workers ⚑️

Add enterprise-grade authentication to your Cloudflare Workers in just a few lines of code. Built for speed with edge caching, zero dependencies, and native Web Crypto API.

πŸš€ Features

  • Stupid Simple API - Authenticate requests in 3 lines of code
  • Blazingly Fast - JWT verification using native Web Crypto API
  • Edge Caching - Automatic JWKS and token caching for sub-millisecond auth
  • Zero Dependencies - Pure TypeScript, no external dependencies
  • TypeScript First - Full type safety and IntelliSense support
  • Flexible - Works with any JWT-based auth provider (Rownd, Auth0, Firebase, etc.)
  • Middleware Pattern - Easy integration with existing Workers

πŸ“¦ Installation

npm install @rownd/cloudflare

🎯 Quick Start

Basic Protected Route

import { RowndAuth } from '@rownd/cloudflare';

const auth = new RowndAuth({
  appKey: 'your-app-key',
  appSecret: 'your-app-secret',
});

export default {
  async fetch(request: Request): Promise<Response> {
    // Authenticate the request
    const authContext = await auth.authenticate(request);

    if (!authContext.isAuthenticated) {
      return new Response('Unauthorized', { status: 401 });
    }

    // Access user info
    const { user } = authContext;
    return new Response(`Hello, ${user?.email}!`);
  },
};

Using Middleware (Recommended)

import { RowndAuth } from '@rownd/cloudflare';

const auth = new RowndAuth({
  appKey: 'your-app-key',
  appSecret: 'your-app-secret',
});

export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    // Public route
    if (url.pathname === '/public') {
      return new Response('Public content');
    }

    // Protected route - automatically returns 401 if not authenticated
    if (url.pathname === '/protected') {
      return auth.requireAuth()(request, async (req, authContext) => {
        return new Response(`Welcome, ${authContext.user?.userId}!`);
      });
    }

    // Optional auth - doesn't reject unauthenticated requests
    if (url.pathname === '/optional') {
      return auth.withAuth()(request, async (req, authContext) => {
        if (authContext.isAuthenticated) {
          return new Response(`Hello, ${authContext.user?.email}!`);
        }
        return new Response('Hello, guest!');
      });
    }

    return new Response('Not found', { status: 404 });
  },
};

πŸ”§ Configuration

const auth = new RowndAuth({
  // Required: Your application credentials
  appKey: 'your-app-key',
  appSecret: 'your-app-secret',

  // Optional: Custom JWKS endpoint (defaults to Rownd's endpoint)
  jwksUrl: 'https://your-auth-provider.com/.well-known/jwks.json',

  // Optional: JWKS cache TTL in seconds (default: 3600 = 1 hour)
  jwksCacheTtl: 3600,

  // Optional: Token cache TTL in seconds (default: 300 = 5 minutes)
  tokenCacheTtl: 300,

  // Optional: Custom auth header name (default: 'authorization')
  authHeader: 'x-auth-token',

  // Optional: Cookie name to check for auth token
  authCookie: 'auth_token',
});

πŸ“š API Reference

RowndAuth

Main authentication class.

Methods

authenticate(request: Request): Promise<AuthContext>

Authenticates a request and returns the auth context.

const authContext = await auth.authenticate(request);

if (authContext.isAuthenticated) {
  console.log('User ID:', authContext.user?.userId);
  console.log('Email:', authContext.user?.email);
  console.log('Custom data:', authContext.user?.data);
}
requireAuth()

Returns a middleware function that rejects unauthenticated requests with 401.

return auth.requireAuth()(request, async (req, authContext) => {
  // This code only runs if authenticated
  return new Response(`Hello, ${authContext.user?.userId}!`);
});
withAuth()

Returns a middleware function that passes auth context but doesn't reject unauthenticated requests.

return auth.withAuth()(request, async (req, authContext) => {
  if (authContext.isAuthenticated) {
    return new Response(`Hello, ${authContext.user?.email}!`);
  }
  return new Response('Hello, guest!');
});

Types

AuthContext

interface AuthContext {
  user: AuthenticatedUser | null;
  isAuthenticated: boolean;
}

AuthenticatedUser

interface AuthenticatedUser {
  userId: string;
  email?: string;
  data?: Record<string, any>;
  claims: Record<string, any>;  // Full JWT payload
  token: string;                // Raw JWT token
}

🎨 Usage Examples

With Hono Framework

import { Hono } from 'hono';
import { RowndAuth } from '@rownd/cloudflare';

const app = new Hono();
const auth = new RowndAuth({
  appKey: 'your-app-key',
  appSecret: 'your-app-secret',
});

// Custom middleware
app.use('/api/*', async (c, next) => {
  const authContext = await auth.authenticate(c.req.raw);

  if (!authContext.isAuthenticated) {
    return c.json({ error: 'Unauthorized' }, 401);
  }

  c.set('user', authContext.user);
  await next();
});

app.get('/api/profile', (c) => {
  const user = c.get('user');
  return c.json({ user });
});

export default app;

With itty-router

import { Router } from 'itty-router';
import { RowndAuth } from '@rownd/cloudflare';

const router = Router();
const auth = new RowndAuth({
  appKey: 'your-app-key',
  appSecret: 'your-app-secret',
});

// Custom auth middleware
const withAuth = async (request: Request) => {
  const authContext = await auth.authenticate(request);

  if (!authContext.isAuthenticated) {
    return new Response('Unauthorized', { status: 401 });
  }

  request.user = authContext.user;
};

router.get('/api/protected', withAuth, (request) => {
  return new Response(`Hello, ${request.user.email}!`);
});

export default {
  fetch: router.handle,
};

Custom JWT Validation

import { RowndAuth } from '@rownd/cloudflare';

const auth = new RowndAuth({
  appKey: 'your-app-key',
  appSecret: 'your-app-secret',
});

export default {
  async fetch(request: Request): Promise<Response> {
    const authContext = await auth.authenticate(request);

    if (!authContext.isAuthenticated) {
      return new Response('Unauthorized', { status: 401 });
    }

    // Access full JWT claims for custom validation
    const { claims } = authContext.user!;

    // Check custom claims
    if (claims.role !== 'admin') {
      return new Response('Forbidden', { status: 403 });
    }

    // Check audience
    if (!claims.aud?.includes('my-api')) {
      return new Response('Invalid audience', { status: 403 });
    }

    return new Response('Admin access granted');
  },
};

πŸ” Token Sources

The SDK automatically checks for tokens in the following order:

  1. Authorization Header: Authorization: Bearer <token>
  2. Custom Header: If authHeader is configured
  3. Cookie: If authCookie is configured

Example with Cookie

const auth = new RowndAuth({
  appKey: 'your-app-key',
  appSecret: 'your-app-secret',
  authCookie: 'auth_token',  // Check this cookie for the token
});

⚑️ Performance

The SDK is optimized for edge performance:

  • JWKS Caching: Public keys are cached at the edge for 1 hour (configurable)
  • Token Caching: Verified tokens are cached for 5 minutes (configurable)
  • Zero Cold Start: No dependencies means instant initialization
  • Native Crypto: Uses Web Crypto API for hardware-accelerated verification

Benchmarks

  • First request (cold): ~50ms (includes JWKS fetch)
  • Cached requests: <1ms
  • Memory usage: <1MB

🌐 Custom Auth Providers

While designed for Rownd, the SDK works with any JWT-based auth provider:

Auth0

const auth = new RowndAuth({
  appKey: 'not-used',
  appSecret: 'not-used',
  jwksUrl: 'https://YOUR-DOMAIN.auth0.com/.well-known/jwks.json',
});

Firebase

const auth = new RowndAuth({
  appKey: 'not-used',
  appSecret: 'not-used',
  jwksUrl: 'https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com',
});

Clerk

const auth = new RowndAuth({
  appKey: 'not-used',
  appSecret: 'not-used',
  jwksUrl: 'https://YOUR-DOMAIN.clerk.accounts.dev/.well-known/jwks.json',
});

πŸ› οΈ Development

# Install dependencies
npm install

# Build
npm run build

# Watch mode
npm run dev

# Run tests
npm test

# Format code
npm run format

# Lint
npm run lint

πŸ“„ License

MIT

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ’¬ Support


Made with ❀️ by Rownd

About

Add authentication to your Cloudflare worker in seconds

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published