npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@abstraks-dev/api-key-auth

v1.0.1

Published

SSM-based API key authentication with caching and timing-safe comparison for AWS Lambda

Downloads

15

Readme

@abstraks-dev/api-key-auth

SSM-based API key authentication with caching and timing-safe comparison for AWS Lambda functions.

Features

  • SSM Parameter Store Integration - Securely fetch API keys from AWS Systems Manager
  • Automatic Caching - Cache API keys with configurable TTL (default: 5 minutes)
  • Timing-Safe Comparison - Prevent timing attacks with constant-time string comparison
  • Lambda Middleware - Easy-to-use middleware for automatic authentication
  • Multiple Header Support - Support for lowercase and uppercase header names
  • Comprehensive Error Handling - Proper error codes and messages
  • Zero Configuration - Works out of the box with sensible defaults

Installation

npm install @abstraks-dev/api-key-auth @aws-sdk/client-ssm

Quick Start

Basic Usage

import { createApiKeyValidator } from '@abstraks-dev/api-key-auth';

// Create validator with SSM parameter name
const validator = createApiKeyValidator(process.env.API_KEY_PARAM);

export const handler = async (event) => {
	try {
		// Validate API key from request headers
		await validator.validateApiKey(event);

		// Your Lambda logic here
		return {
			statusCode: 200,
			body: JSON.stringify({ message: 'Success' }),
		};
	} catch (error) {
		return {
			statusCode: error.statusCode || 500,
			body: JSON.stringify({ error: error.message }),
		};
	}
};

Using Middleware

import { createApiKeyValidator } from '@abstraks-dev/api-key-auth';

const validator = createApiKeyValidator(process.env.API_KEY_PARAM);

const myHandler = async (event, context, callback) => {
	// API key already validated by middleware
	// Your logic here
	return { statusCode: 200, body: 'Success' };
};

// Wrap handler with authentication middleware
export const handler = validator.middleware(myHandler);

API Reference

createApiKeyValidator(parameterName, options)

Factory function to create a new API key validator.

Parameters:

  • parameterName (string, required): SSM parameter name (e.g., /abstraks/service/dev/apiKey)
  • options (object, optional):
    • region (string): AWS region (defaults to process.env.AWS_REGION or 'us-west-2')
    • cacheTTL (number): Cache TTL in milliseconds (defaults to 300000 = 5 minutes)

Returns: ApiKeyValidator instance

Example:

const validator = createApiKeyValidator('/abstraks/query/prod/apiKey', {
	region: 'us-east-1',
	cacheTTL: 10 * 60 * 1000, // 10 minutes
});

ApiKeyValidator Class

Constructor

new ApiKeyValidator(options);

Options:

  • parameterName (string, required): SSM parameter name
  • region (string): AWS region
  • cacheTTL (number): Cache TTL in milliseconds

Methods

validateApiKey(event, options)

Validates API key from Lambda event headers.

Parameters:

  • event (object, required): Lambda event object
  • options (object, optional):
    • headerName (string): Header name to check (defaults to 'x-api-key')

Returns: Promise<boolean> - Returns true if valid

Throws: Error with statusCode property:

  • 401: Missing or invalid API key
  • 500: Service configuration error

Example:

try {
	await validator.validateApiKey(event);
	console.log('API key is valid');
} catch (error) {
	console.error(`Validation failed: ${error.message}`);
	// error.statusCode will be 401 or 500
}
getExpectedApiKey()

Fetches API key from SSM Parameter Store (with caching).

Returns: Promise<string> - The API key value

Throws: Error if parameter not found or SSM fetch fails

Example:

const apiKey = await validator.getExpectedApiKey();
timingSafeEqual(a, b)

Performs timing-safe string comparison.

Parameters:

  • a (string): First string
  • b (string): Second string

Returns: boolean - True if strings are equal

Example:

const isMatch = validator.timingSafeEqual('key1', 'key2');
middleware(handler)

Creates Lambda middleware for automatic authentication.

Parameters:

  • handler (Function): Lambda handler function to wrap

Returns: Function - Wrapped handler function with automatic API key validation

Example:

const wrappedHandler = validator.middleware(myHandler);
clearCache()

Clears cached API key (forces fresh fetch on next validation).

Example:

validator.clearCache();
isCacheValid()

Checks if cached API key is still valid.

Returns: boolean - True if cache is valid

Example:

if (!validator.isCacheValid()) {
	console.log('Cache expired');
}
getCacheStats()

Gets cache statistics.

Returns: Object with:

  • hasCachedKey (boolean): Whether key is cached
  • cacheAge (number|null): Age of cache in milliseconds
  • cacheTTL (number): Cache TTL in milliseconds
  • isValid (boolean): Whether cache is valid

Example:

const stats = validator.getCacheStats();
console.log(`Cache age: ${stats.cacheAge}ms`);

Usage Patterns

Pattern 1: Basic Validation

import { createApiKeyValidator } from '@abstraks-dev/api-key-auth';

const validator = createApiKeyValidator(process.env.API_KEY_PARAM);

export const handler = async (event) => {
	try {
		await validator.validateApiKey(event);
		// Proceed with authenticated logic
	} catch (error) {
		return {
			statusCode: error.statusCode,
			body: JSON.stringify({ error: error.message }),
		};
	}
};

Pattern 2: Custom Header Name

const validator = createApiKeyValidator(process.env.API_KEY_PARAM);

export const handler = async (event) => {
	try {
		// Validate using custom header
		await validator.validateApiKey(event, { headerName: 'x-custom-api-key' });
		// ...
	} catch (error) {
		// Handle error
	}
};

Pattern 3: Middleware with Error Handling

import { createApiKeyValidator } from '@abstraks-dev/api-key-auth';
import {
	createErrorResponse,
	createSuccessResponse,
} from '@abstraks/lambda-responses';

const validator = createApiKeyValidator(process.env.API_KEY_PARAM);

const myHandler = async (event, context, callback) => {
	// API key already validated by middleware
	const data = await processRequest(event);
	return callback(null, createSuccessResponse(data));
};

// Wrap handler with middleware - validation happens automatically
export const handler = validator.middleware(myHandler);

Pattern 4: Multi-Service Setup

// query/service/helpers/apiKeyAuth.js
import { createApiKeyValidator } from '@abstraks-dev/api-key-auth';

export const queryApiKeyValidator = createApiKeyValidator(
	process.env.QUERY_API_KEY_PARAM
);

// query/service/lambdas/query.js
import { queryApiKeyValidator } from '../helpers/apiKeyAuth.js';

export const handler = async (event) => {
	await queryApiKeyValidator.validateApiKey(event);
	// ... query logic
};

Integration with CDK

IAM Permissions

Your Lambda function needs permissions to read from SSM Parameter Store:

// CDK Stack
eventDataLambda.addToRolePolicy(
	new iam.PolicyStatement({
		actions: ['ssm:GetParameter'],
		resources: [
			`arn:aws:ssm:${this.region}:${this.account}:parameter${apiKeyParam}`,
		],
		conditions: {
			StringEquals: {
				'ssm:ParameterType': 'SecureString',
			},
		},
	})
);

// For SecureString parameters, also add KMS decrypt permission
eventDataLambda.addToRolePolicy(
	new iam.PolicyStatement({
		actions: ['kms:Decrypt'],
		resources: ['arn:aws:kms:*:*:key/*'],
	})
);

Environment Variables

const lambdaEnv = {
	API_KEY_PARAM: `/abstraks/query/${environment}/apiKey`,
};

Security Features

Timing-Safe Comparison

The package uses crypto.timingSafeEqual() to prevent timing attacks:

// ❌ Vulnerable to timing attacks
if (apiKey === expectedKey) { ... }

// ✅ Timing-safe comparison
if (validator.timingSafeEqual(apiKey, expectedKey)) { ... }

Cache TTL for Key Rotation

Keys are automatically refetched after TTL expires, supporting key rotation:

const validator = createApiKeyValidator('/api-key', {
	cacheTTL: 5 * 60 * 1000, // Refetch every 5 minutes
});

SecureString Support

The package uses WithDecryption: true to automatically decrypt SecureString parameters from SSM.

Error Handling

All errors include a statusCode property for proper HTTP responses:

try {
	await validator.validateApiKey(event);
} catch (error) {
	console.error(error.message); // Human-readable error
	console.log(error.statusCode); // 401 or 500
}

Error Types:

  • 401 Unauthorized: Missing or invalid API key
  • 500 Internal Server Error: SSM configuration error

Testing

Mocking in Tests

import { jest } from '@jest/globals';

const mockSend = jest.fn();
jest.unstable_mockModule('@aws-sdk/client-ssm', () => ({
	SSMClient: jest.fn(() => ({ send: mockSend })),
	GetParameterCommand: jest.fn(),
}));

// In your test
mockSend.mockResolvedValue({
	Parameter: { Value: 'test-api-key' },
});

Example Test

import { createApiKeyValidator } from '@abstraks-dev/api-key-auth';

test('should validate correct API key', async () => {
	const validator = createApiKeyValidator('/test/api-key');

	const event = {
		headers: { 'x-api-key': 'correct-key' },
	};

	await expect(validator.validateApiKey(event)).resolves.toBe(true);
});

Migration from Service-Specific Code

Before (Duplicated in every service)

// query/service/lambdas/query.js
import crypto from 'crypto';
import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm';

const ssmClient = new SSMClient({ region: process.env.AWS_REGION });
let cachedApiKey = null;

async function getExpectedApiKey() {
	if (cachedApiKey) return cachedApiKey;
	// ... SSM logic
}

function timingSafeEqual(a, b) {
	// ... timing-safe comparison
}

export const handler = async (event) => {
	const apiKey = event.headers['x-api-key'];
	const expected = await getExpectedApiKey();
	if (!timingSafeEqual(apiKey, expected)) {
		return { statusCode: 401, body: 'Unauthorized' };
	}
	// ... rest of handler
};

After (Using shared module)

// query/service/lambdas/query.js
import { createApiKeyValidator } from '@abstraks-dev/api-key-auth';

const validator = createApiKeyValidator(process.env.API_KEY_PARAM);

const myHandler = async (event) => {
	// Your logic here
};

export const handler = validator.middleware(myHandler);

Result: Eliminated 50+ lines of duplicated code per service! ✨

Performance

  • First Request: ~100-200ms (SSM fetch)
  • Cached Requests: <1ms (in-memory lookup)
  • Memory: <1KB per validator instance

License

MIT

Related Packages