@shergill/auth
v1.0.2
Published
Reusable authentication and rate limiting library for Cloudflare Workers
Maintainers
Readme
@shergill/auth
A reusable authentication and rate limiting library for Cloudflare Workers. This package provides API key validation, usage logging, quota enforcement with tier-based rate limiting (free vs paid users), and a central checkAllowed function to protect your Workers endpoints.
Features
- 🔐 API Key Validation - Secure key validation using Cloudflare KV
- 📊 Usage Logging - Track API usage with Cloudflare Analytics Engine
- 🚦 Tier-Based Rate Limiting - Different limits for free and paid users
- ⚙️ Configurable Limits - Easily customizable rate limits
- ⚡ Performance - Optimized for Cloudflare Workers edge runtime
- 🛡️ Security - Fail-safe design with proper error handling
Installation
npm install @shergill/authRate Limiting Tiers
The library supports two user tiers with different rate limits:
Default Rate Limits
// Free tier users
FREE: {
perMinute: 100,
perDay: 2000
}
// Paid tier users
PAID: {
perMinute: 1000,
perDay: 50000
}Rate limits are applied globally across all endpoints for each user tier.
Cloudflare Configuration
1. Wrangler Configuration
Add the following bindings to your wrangler.toml:
name = "my-worker"
main = "src/index.js"
compatibility_date = "2023-05-18"
[[kv_namespaces]]
binding = "API_KEYS"
id = "your-kv-namespace-id"
preview_id = "your-preview-kv-namespace-id"
[[analytics_engine_datasets]]
binding = "MY_ANALYTICS"
dataset = "your-analytics-dataset-name"2. API Keys KV Store
Your API keys should be stored in Cloudflare KV with the following schema (note the new isPaid field):
Key: The API key string (e.g., "sk-ZgPQwCeGuyU4fL73paDtL0n4YBgPiBWBW7Hzpqbdf8YFdVJQ")
Value: JSON object with user metadata:
{
"id": "a7deb115-b8b1-46c4-90b3-3344080b2bad",
"key": "sk-ZgPQwCeGuyU4fL73paDtL0n4YBgPiBWBW7Hzpqbdf8YFdVJQ",
"name": "hello",
"userId": "68daba59e7d62d6fc816",
"createdAt": "2023-01-01T00:00:00.000Z",
"lastUsed": null,
"isActive": true,
"isPaid": false
}Usage
Basic Integration
import { checkAllowed } from "@shergill/auth";
export default {
async fetch(request, env, ctx) {
const result = await checkAllowed(request, env, ctx);
if (!result.allowed) {
return new Response(JSON.stringify({
error: result.reason,
details: result.details // Contains tier-specific info
}), {
status: 429,
headers: { "Content-Type": "application/json" }
});
}
// Your protected endpoint logic here
return new Response(`Hello, ${result.userTier} user ${result.userId}!`);
}
};Advanced Usage with Custom Rate Limits
import {
checkAllowed,
DEFAULT_RATE_LIMITS
} from "@shergill/auth";
// Custom rate limits for your application
const CUSTOM_LIMITS = {
FREE: { perMinute: 50, perDay: 1000 },
PAID: { perMinute: 2000, perDay: 100000 }
};
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Public endpoints don't need authentication
if (url.pathname === "/public") {
return new Response("Public endpoint");
}
// Use custom rate limits
const result = await checkAllowed(request, env, ctx, CUSTOM_LIMITS);
if (!result.allowed) {
return new Response(JSON.stringify({
error: result.reason,
details: result.details,
userTier: result.userTier,
endpoint: result.endpoint
}), {
status: 429,
headers: { "Content-Type": "application/json" }
});
}
// Access user metadata and effective limits
const { userId, endpoint, metadata, userTier, effectiveLimits } = result;
return new Response(JSON.stringify({
message: "Success",
user: metadata.name,
tier: userTier,
endpoint: endpoint,
limits: effectiveLimits
}), {
headers: { "Content-Type": "application/json" }
});
}
};API Reference
checkAllowed(request, env, ctx?, customRateLimits?)
Main entry point function that performs complete authentication and rate limiting.
Parameters:
request(Request): The incoming HTTP requestenv(Object): Cloudflare environment bindingsctx(Object, optional): Cloudflare execution context forwaitUntilcustomRateLimits(Object, optional): Custom rate limits override
Returns: Promise resolving to:
// Success
{
allowed: true,
userId: string,
endpoint: string,
metadata: object,
userTier: 'free' | 'paid',
effectiveLimits: { perMinute: number, perDay: number }
}
// Failure
{
allowed: false,
reason: string,
details?: string
}DEFAULT_RATE_LIMITS
Default tier-based rate limits:
export const DEFAULT_RATE_LIMITS = {
FREE: { perMinute: 100, perDay: 2000 },
PAID: { perMinute: 1000, perDay: 50000 }
};getEffectiveRateLimits(userMetadata, customLimits?)
Calculate the effective rate limits for a user based on their tier.
Returns: Object with perMinute and perDay limits.
Individual Functions
validateKey(request, env)- Validate API key from request headerslogUsage(env, userId, endpoint)- Log API usage to Analytics EnginegetUsage(env, userId, endpoint)- Query current usage statisticscheckLimit(usageData, limits, userMetadata?)- Check if usage is within limits
Customizing Rate Limits
You can easily customize rate limits by modifying the constants or passing custom limits:
// Method 1: Modify the exported constants (affects all usage)
import { DEFAULT_RATE_LIMITS } from "@shergill/auth";
DEFAULT_RATE_LIMITS.FREE.perMinute = 50;
DEFAULT_RATE_LIMITS.PAID.perMinute = 2000;
// Method 2: Pass custom limits to checkAllowed (recommended)
const customLimits = {
FREE: { perMinute: 25, perDay: 500 },
PAID: { perMinute: 500, perDay: 25000 }
};
const result = await checkAllowed(request, env, ctx, customLimits);Rate Limiting
The library enforces rate limits based on:
- Per-minute limits: Short-term burst protection
- Per-day limits: Daily quota enforcement
Rate limits are enforced per user per endpoint based on their tier (free vs paid).
Error Handling
The library follows a "fail-safe" approach:
- Authentication errors: Block the request
- Analytics errors: Allow the request (logged to console)
- Rate limit query errors: Allow the request
Analytics Schema
Usage data is stored in Cloudflare Analytics Engine with:
- blob1: User ID
- blob2: Endpoint path
- double1: Timestamp (Date.now())
Development
Local Testing
# Run tests
npm test
# Check for errors
node -c src/index.jsContributing
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
ISC License - see LICENSE file for details.
