New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

@abstraks-dev/api-key-auth

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@abstraks-dev/api-key-auth

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

latest
Source
npmnpm
Version
1.0.1
Version published
Maintainers
1
Created
Source

@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

  • @abstraks/lambda-responses - Standardized Lambda response helpers
  • @abstraks/mongodb-connection - MongoDB connection management

Keywords

abstraks

FAQs

Package last updated on 20 Nov 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts