@jcdubs/janus
v1.2.0
Published
Open source Serverless authentication: A Cedar-based authorisation engine for deterministic, deny-by-default access decisions through a CDK construct and SDK libraries.
Maintainers
Readme
Janus
Open source serverless authentication: A Cedar-based authorization engine for deterministic, deny-by-default access decisions through a CDK construct and SDK libraries.
Overview
Janus is a TypeScript library that provides fine-grained, policy-based authorization for AWS Lambda functions using Cedar. It enables you to define complex authorization rules and evaluate them efficiently within your serverless applications.
Janus is based on the pattern discussed in this blog post: Serverless: Granular Authorisation with Cedar — High control, minimal cost.
Key Features
- 🔐 Cedar Policy Engine - Leverage Amazon's Cedar policy language for authorization
- ⚡ Serverless Optimised - Designed for AWS Lambda with singleton caching
- 🎯 Type-Safe - Full TypeScript support with comprehensive type definitions
- 🔄 Fluent API - Intuitive method chaining for building authorization requests
- 🧪 Well Tested - Comprehensive test coverage with real-world examples
- 📦 Zero Config - Easy integration with minimal setup
Installation
Install the Package
npm install @jcdubs/janusor
pnpm add @jcdubs/janusor
yarn add @jcdubs/janusInstall Peer Dependencies
Janus requires the following peer dependencies to be installed in your project:
# For AWS Lambda PowerTools (logging)
npm install @aws-lambda-powertools/[email protected]
# For AWS CDK (if using CDK constructs)
npm install [email protected] [email protected]With pnpm:
pnpm add @aws-lambda-powertools/[email protected]
pnpm add [email protected] [email protected]With yarn:
yarn add @aws-lambda-powertools/[email protected]
yarn add [email protected] [email protected]Note: The CDK dependencies (
aws-cdk-libandconstructs) are only required if you're using Janus in a CDK application. For Lambda runtime usage only, you just need@aws-lambda-powertools/logger.
Quick Start
1. Define Your Cedar Policy
Create a policies.cedar file:
// Allow users to view their own orders
permit (
principal,
action == Action::"viewOrder",
resource
) when {
principal.id == resource.customerId
};2. Define Your Cedar Schema
Create a schema.cedarschema file:
namespace OrderService {
entity User = {
id: String,
roles: Set<Role>
};
entity Order = {
customerId: String,
status: String
};
entity Role;
action viewOrder appliesTo {
principal: User,
resource: Order
};
}Implement the Auth Lambda
The following example demonstrates a simple AWS Lambda handler that uses the middleware to load Cedar authorization and then performs an authorization check inside the handler.
import middy from '@middy/core';
import type { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import {
loadCedarAuthorization,
AuthorizationService,
EntityBuilder,
getUserName,
} from '@jcdubs/janus';
const authorizationConfig = {
namespace: 'OrderService::',
principleType: 'User',
resourceType: 'Order',
roleType: 'Role',
};
const handler = async (
event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> => {
// AuthorizationService is cached by the middleware, but retrieving it here is safe
// and inexpensive (cached) and makes the intent explicit in the handler.
const authService = await AuthorizationService.getService(authorizationConfig);
const resourceId = event.pathParameters?.orderId ?? 'order-123';
const isAuthorized = authService
.setAction('viewOrder')
.setResource(resourceId)
.addEntity(
new EntityBuilder(resourceId, authorizationConfig)
.withStringAttr('customerId', getUserName())
.build(),
)
.isAuthorized();
return {
statusCode: isAuthorized ? 200 : 403,
body: JSON.stringify({ allowed: isAuthorized }),
};
};
export const main = middy(handler).use(loadCedarAuthorization(authorizationConfig));3. Use the Authorization Service
import { AuthorizationService, EntityBuilder } from '@jcdubs/janus';
// Define the authorization configuration and initialize the service (cached as a singleton)
const authorizationConfig = {
namespace: 'OrderService::',
principleType: 'User',
resourceType: 'Order',
roleType: 'Role'
};
const authService = await AuthorizationService.getService(authorizationConfig);
// Examples showing varied `EntityBuilder` usage patterns
// Minimal: build an entity with only UID
const isAuthorizedMinimal = authService
.setAction('viewOrder')
.setResource('order-123')
.addEntity(new EntityBuilder('order-123', authorizationConfig).build())
.isAuthorized();
// Typical: add a few simple attributes
const isAuthorizedTypical = authService
.setAction('viewOrder')
.setResource('order-123')
.addEntity(
new EntityBuilder('order-123', authorizationConfig)
.withStringAttr('customerId', 'user-456')
.withStringAttr('status', 'PENDING')
.withNumberAttr('items', 3)
.build()
)
.isAuthorized();
// Full: include sets, references, extension attrs, parents and tags
const isAuthorizedFull = authService
.setAction('viewOrder')
.setResource('order-123')
.addEntity(
new EntityBuilder('order-123', authorizationConfig)
.withStringAttr('customerId', 'user-456')
.withBooleanAttr('active', true)
.withNumberAttr('items', 5)
.withSetAttr('flags', ['flagA', 'flagB'])
.withAttr('owner', 'u1', authorizationConfig.principleType)
.withExtnAttr('ip', 'ipaddr', '192.168.1.10')
.withParent('role-1', 'Role')
.withTag('label', 'lbl1', 'Label')
.build()
)
.isAuthorized();
logger.info('Create authorisation requests', isAuthorizedMinimal, isAuthorizedTypical, isAuthorizedFull);API Reference
AuthorizationService
Static Methods
getService(config, refresh?)
Retrieves or creates a cached instance of the Authorization Service.
Parameters:
config: AuthorizationConfigType- Configuration objectnamespace: string- Cedar namespace (e.g.,'OrderService::')principleType: string- Principal entity type (e.g.,'User')resourceType: string- Resource entity type (e.g.,'Order')roleType: string- Role entity type (e.g.,'Role')
refresh?: boolean- Force reload policies and schemas (default:false)
Returns: Promise<AuthorizationService>
Instance Methods
setAction(action)
Sets the action to be authorized.
Parameters:
action: string- The action name
Returns: this (for chaining)
setResource(resource)
Sets the resource identifier.
Parameters:
resource: string- The resource ID
Returns: this (for chaining)
setContext(context)
Sets the authorization context.
Parameters:
context: Record<string, cedar.CedarValueJson>- Context data
Returns: this (for chaining)
addEntity(entity)
Adds an entity to the authorization request.
Parameters:
entity: cedar.EntityJson- Entity definition
Returns: this (for chaining)
addEntities(entities)
Adds multiple entities to the authorization request.
Parameters:
entities: cedar.EntityJson[]- Array of entity definitions
Returns: this (for chaining)
isAuthorized()
Evaluates the authorization request.
Returns: boolean - true if authorized, false otherwise
Throws:
MissingAuthenticatedUserDetailsError- If user details are missingMissingAuthorizationActionError- If action is not setMissingAuthorizationResourceError- If resource is not setMissingAuthorizationPolicyError- If policies cannot be loadedMissingAuthorizationSchemaError- If schema cannot be loaded
Middleware
authorizationMiddleware
Middy middleware for automatic authorization in Lambda handlers.
import { authorizationMiddleware } from '@jcdubs/janus';
import middy from '@middy/core';
const handler = middy(async (event) => {
// Your handler logic
})
.use(authorizationMiddleware({
namespace: 'OrderService::',
principleType: 'User',
resourceType: 'Order',
roleType: 'Role',
}));Auth Lambda Construct
Provides a CDK construct to bundle a Node.js Lambda with Cedar policy and schema files and the Cedar WASM runtime.
- Export:
AuthLambda(class) - Props:
AuthLambdaProps— extendsNodejsFunctionPropsand addsauthorisation: { policyFilePath: string; schemaFilePath: string }.
Usage: Use AuthLambda in CDK stacks to ensure Cedar policies and schema are bundled with the Lambda package and the Cedar WASM runtime copied into node_modules/@cedar-policy/cedar-wasm. In particular, AuthLambda makes sure the @cedar-policy/cedar-wasm package, your Cedar policy file (for example policies.cedar) and your Cedar schema file (for example schema.cedarschema) are included in the Lambda deployment package so they are available at runtime.
EntityBuilder
Fluent builder for creating Cedar entity JSON objects used in authorization requests.
- Export:
EntityBuilder(class) - Constructor:
new EntityBuilder(id: string, authorizationConfig: AuthorizationConfigType, type?: string) - Common Methods:
withAttr(name, id, type),withExtnAttr(name, fn, arg),withBooleanAttr(name, value),withNumberAttr(name, value),withStringAttr(name, value),withSetAttr(name, value),withParent(id, type),withTag(name, id, type),build()— returnsEntityJson.
Example usage is shown in the Quick Start section above.
File Loader
Small utility to read bundled files (Cedar policy and schema) from the Lambda package.
- Export:
loadFileAsString(fileName: string): string
Throws an Error if the file cannot be read. Typically used by the AuthorizationService to load policies.cedar and schema.cedarschema.
Types
Shared TypeScript types used across the library.
TypeAndId—{ type: string; id: string }EntityUidJson—{ __entity: TypeAndId } | TypeAndIdCedarValueJson— union of entity refs, extn values, primitives, arrays, objects, or nullFnAndArg—{ fn: string; arg: CedarValueJson }EntityJson—{ uid: EntityUidJson; attrs: Record<string, CedarValueJson>; parents: EntityUidJson[]; tags?: Record<string, CedarValueJson> }
Errors
The library exports a set of specific error classes used by the authorization flow.
MissingAuthenticatedUserDetailsErrorMissingAuthorizationActionErrorMissingAuthorizationPolicyErrorMissingAuthorizationResourceErrorMissingAuthorizationSchemaErrorUnauthorizedError
These are exported from the errors module and are thrown by the AuthorizationService and middleware where applicable.
User Details
The library provides utilities to extract user information from Lambda events:
import { getUserName, getRoles } from '@jcdubs/janus';
const username = getUserName(event);
const roles = getRoles(event);Error Handling
The library provides specific error classes for different authorization failures:
MissingAuthenticatedUserDetailsErrorMissingAuthorizationActionErrorMissingAuthorizationPolicyErrorMissingAuthorizationResourceErrorMissingAuthorizationSchemaErrorUnauthorizedError
Examples
Order Service Example
The examples/order-service project demonstrates a complete integration of Janus in a real-world serverless service. It shows how the Janus CDK construct, middleware and SDK are used together to provide Cedar-based authorization for AWS Lambda CRUD handlers.
- Janus Integration: The example uses the provided
AuthLambda construct and theauthorizationMiddlewareto bundle and load Cedar policy and schema files. The authorization checks inside the order CRUD Lambdas use theAuthorizationServicefrom the Janus SDK (via the auth secondary adapter) to evaluate requests against the deployed Cedar policies and schema. - Full CRUD API: The example implements a full Create/Read/Update/Delete API for
ordersbacked by the included lambda handlers. - Scripts: See the
examples/order-service/scriptsdirectory — it contains scripts to hydrate the database, create users and groups in the Cognito user pool, and login scripts for individual users associated with specific groups. - Postman Collection: A Postman collection (
Auth.postman_collection.json) is included in the example. It contains requests that exercise each user and group against the Cedar policy and schema files deployed with the order CRUD Lambdas.
See the authorization-tests directory for comprehensive examples including:
- Customer role permissions
- Sales staff authorization
- Manager access controls
- Account manager restrictions
- Accountant read-only access
Cedar Resources
- Cedar Policy Tutorial
- Cedar Policy Language Guide
- Cedar Policy Blog
- Cedar SDK
- Cedar Policy Playground
- Serverless: Granular Authorisation with Cedar — High control, minimal cost (blog post)
Development
Prerequisites
- Node.js 20+
- pnpm 10+
Peer Dependencies
This project uses peer dependencies to avoid version conflicts. The required peer dependencies are:
@aws-lambda-powertools/[email protected]- For structured logging[email protected]- For CDK constructs (optional)[email protected]- For CDK constructs (optional)
Setup
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run tests with coverage
pnpm test:coverage
# Build
pnpm buildTesting
# Run all tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Generate coverage report
pnpm test:coverageContributing
We welcome contributions! Please see CONTRIBUTING.md for details on how to get started.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Security
For security concerns, please see SECURITY.md.
Support
Acknowledgments
- Built with Cedar Policy by Amazon
- Powered by @cedar-policy/cedar-wasm
