Open source Serverless authentication: A Cedar-based authorisation engine for deterministic, deny-by-default access decisions through a CDK construct and SDK libraries.
npm install @jcdubs/janus






Open source serverless authentication: A Cedar-based authorization engine for deterministic, deny-by-default access decisions through a CDK construct and SDK libraries.
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.
- ๐ 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
``bash`
npm install @jcdubs/janus
or
`bash`
pnpm add @jcdubs/janus
or
`bash`
yarn add @jcdubs/janus
Janus requires the following peer dependencies to be installed in your project:
`bashFor AWS Lambda PowerTools (logging)
npm install @aws-lambda-powertools/logger@2.15.0
With pnpm:
`bash
pnpm add @aws-lambda-powertools/logger@2.15.0
pnpm add aws-cdk-lib@2.219.0 constructs@10.4.2
`With yarn:
`bash
yarn add @aws-lambda-powertools/logger@2.15.0
yarn add aws-cdk-lib@2.219.0 constructs@10.4.2
`> Note: The CDK dependencies (
aws-cdk-lib and constructs) 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
$3
Create a
policies.cedar file:`cedar
// Allow users to view their own orders
permit (
principal,
action == Action::"viewOrder",
resource
) when {
principal.id == resource.customerId
};
`$3
Create a
schema.cedarschema file:`cedar
namespace OrderService {
entity User = {
id: String,
roles: Set
};
entity Order = {
customerId: String,
status: String
};
entity Role;
action viewOrder appliesTo {
principal: User,
resource: Order
};
}
`$3
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.
`typescript
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 => {
// 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
`typescript
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
$3
#### Static Methods
#####
getService(config, refresh?)Retrieves or creates a cached instance of the Authorization Service.
Parameters:
-
config: AuthorizationConfigType - Configuration object
- namespace: 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#### Instance Methods
#####
setAction(action)Sets the action to be authorized.
Parameters:
-
action: string - The action nameReturns:
this (for chaining)#####
setResource(resource)Sets the resource identifier.
Parameters:
-
resource: string - The resource IDReturns:
this (for chaining)#####
setContext(context)Sets the authorization context.
Parameters:
-
context: Record - Context dataReturns:
this (for chaining)#####
addEntity(entity)Adds an entity to the authorization request.
Parameters:
-
entity: cedar.EntityJson - Entity definitionReturns:
this (for chaining)#####
addEntities(entities)Adds multiple entities to the authorization request.
Parameters:
-
entities: cedar.EntityJson[] - Array of entity definitionsReturns:
this (for chaining)#####
isAuthorized()Evaluates the authorization request.
Returns:
boolean - true if authorized, false otherwiseThrows:
-
MissingAuthenticatedUserDetailsError - If user details are missing
- MissingAuthorizationActionError - If action is not set
- MissingAuthorizationResourceError - If resource is not set
- MissingAuthorizationPolicyError - If policies cannot be loaded
- MissingAuthorizationSchemaError - If schema cannot be loaded$3
####
authorizationMiddlewareMiddy middleware for automatic authorization in Lambda handlers.
`typescript
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',
}));
`$3
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 โ extends NodejsFunctionProps and adds authorisation: { 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.$3
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() โ returns EntityJson.Example usage is shown in the Quick Start section above.
$3
Small utility to read bundled files (Cedar policy and schema) from the Lambda package.
- Export:
loadFileAsString(fileName: string): stringThrows an
Error if the file cannot be read. Typically used by the AuthorizationService to load policies.cedar and schema.cedarschema.$3
Shared TypeScript types used across the library.
-
TypeAndId โ { type: string; id: string }
- EntityUidJson โ { __entity: TypeAndId } | TypeAndId
- CedarValueJson โ union of entity refs, extn values, primitives, arrays, objects, or null
- FnAndArg โ { fn: string; arg: CedarValueJson }
- EntityJson โ { uid: EntityUidJson; attrs: Record$3
The library exports a set of specific error classes used by the authorization flow.
-
MissingAuthenticatedUserDetailsError
- MissingAuthorizationActionError
- MissingAuthorizationPolicyError
- MissingAuthorizationResourceError
- MissingAuthorizationSchemaError
- UnauthorizedErrorThese 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:
`typescript
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:
-
MissingAuthenticatedUserDetailsError
- MissingAuthorizationActionError
- MissingAuthorizationPolicyError
- MissingAuthorizationResourceError
- MissingAuthorizationSchemaError
- UnauthorizedErrorExamples
$3
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
Auth Lambda construct and the authorizationMiddleware to bundle and load Cedar policy and schema files. The authorization checks inside the order CRUD Lambdas use the AuthorizationService from 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 orders backed by the included lambda handlers.
- Scripts: See the examples/order-service/scripts directory โ 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
$3
- Node.js 20+
- pnpm 10+
$3
This project uses peer dependencies to avoid version conflicts. The required peer dependencies are:
-
@aws-lambda-powertools/logger@2.15.0 - For structured logging
- aws-cdk-lib@2.219.0 - For CDK constructs (optional)
- constructs@10.4.2 - For CDK constructs (optional)$3
`bash
Install dependencies
pnpm installRun tests
pnpm testRun tests with coverage
pnpm test:coverageBuild
pnpm build
`$3
`bash
Run all tests
pnpm testRun tests in watch mode
pnpm test:watchGenerate coverage report
pnpm test:coverage
``We welcome contributions! Please see CONTRIBUTING.md for details on how to get started.
This project is licensed under the MIT License - see the LICENSE file for details.
For security concerns, please see SECURITY.md.
- ๐ซ Open an issue
- ๐ฌ Discussions
- Built with Cedar Policy by Amazon
- Powered by @cedar-policy/cedar-wasm