@arbor-authz/nestjs
v0.2.0
Published
NestJS client module for Arbor Cedar Authorization Platform
Readme
@arbor-authz/nestjs
NestJS client module for the Arbor Cedar authorization platform.
Provides a guard, decorators, and services that integrate Cedar authorization into any NestJS application. Your app resolves entities from its own database; Arbor evaluates Cedar policies and returns cryptographically signed decisions.
Installation
npm install @arbor-authz/nestjsQuick Start
1. Implement resolvers
// src/authz/my-principal-resolver.ts
import { Injectable } from '@nestjs/common';
import { PrincipalResolver } from '@arbor-authz/nestjs';
@Injectable()
export class MyPrincipalResolver implements PrincipalResolver {
resolve(user: any, request: any): string {
if (user.type === 'staff') return `StaffMember::"${user.id}"`;
return `Customer::"${user.id}"`;
}
}// src/authz/my-entity-resolver.ts
import { Injectable } from '@nestjs/common';
import { EntityResolver, CedarEntity } from '@arbor-authz/nestjs';
@Injectable()
export class MyEntityResolver implements EntityResolver {
constructor(private usersRepo: UsersRepository) {}
async resolve(params): Promise<CedarEntity[]> {
const entities: CedarEntity[] = [];
const principalId = params.principal.match(/::"(.+)"/)?.[1];
const user = await this.usersRepo.findById(principalId);
entities.push({
uid: { type: 'Customer', id: principalId },
attrs: { verified: user.verified },
parents: user.groups.map(g => ({ type: 'Group', id: g })),
});
return entities;
}
}2. Register the module
// app.module.ts
import { AuthzModule } from '@arbor-authz/nestjs';
@Module({
imports: [
AuthzModule.forRoot({
serviceUrl: 'http://arbor-server:3100',
entityResolver: MyEntityResolver,
principalResolver: MyPrincipalResolver,
}),
],
})
export class AppModule {}Or with async configuration:
AuthzModule.forRootAsync({
useFactory: (config: ConfigService) => ({
serviceUrl: config.get('AUTHZ_SERVICE_URL'),
entityResolver: MyEntityResolver,
principalResolver: MyPrincipalResolver,
timeout: 5000,
onDeny: 'throw',
}),
inject: [ConfigService],
})3. Decorate your routes
import { AuthzGuard, CedarAction, CedarResource, CedarContext, SkipAuthz } from '@arbor-authz/nestjs';
@Controller('payments')
@UseGuards(JwtAuthGuard, AuthzGuard)
export class PaymentsController {
@Post()
@CedarAction('createPayment')
@CedarResource('Payment::"new"')
@CedarContext((req) => ({ amount: req.body.amount }))
create(@Body() dto: CreatePaymentDto) { }
@Get(':id')
@CedarAction('viewPayment')
@CedarResource((req) => `Payment::"${req.params.id}"`)
findOne(@Param('id') id: string) { }
@Get('health')
@SkipAuthz()
health() { }
}How It Works
AuthzGuardreads@CedarAction/@CedarResource/@CedarContextmetadataPrincipalResolver.resolve()mapsreq.userto a Cedar principal stringEntityResolver.resolve()queries your database and builds Cedar entitiesAuthzClientServicePOSTs to arbor-server's/authorizeendpointTokenVerifierServiceverifies the signed JWT response:- RS256 signature (via cached JWKS public keys)
- Token expiry
- Nonce (replay prevention)
- Request binding (principal, action, resource match)
- Context hash + entity hash (tamper detection)
- Guard allows or denies the request
API Reference
Decorators
| Decorator | Purpose |
|-----------|---------|
| @CedarAction('name') | Cedar action for the route |
| @CedarResource('Type::"id"') | Static resource string |
| @CedarResource((req) => ...) | Dynamic resource from request |
| @CedarContext((req) => ({...})) | Extra context beyond auto-collected |
| @SkipAuthz() | Skip authorization for this route |
Module Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| serviceUrl | string | required | Arbor server URL |
| entityResolver | Type<EntityResolver> | required | Your entity resolver class |
| principalResolver | Type<PrincipalResolver> | required | Your principal resolver class |
| jwksRefreshInterval | number | 3600000 | JWKS refresh interval (ms) |
| timeout | number | 5000 | HTTP request timeout (ms) |
| onDeny | 'throw' \| 'log' | 'throw' | Behavior on deny |
Exported Services
For advanced usage, you can inject these directly:
AuthzClientService— make authorization requestsJwksCacheService— access cached JWKS keysTokenVerifierService— verify tokens manuallyNonceCacheService— check/add nonces
Security
The verification chain ensures:
- Authenticity — RS256 signature proves the decision came from arbor-server
- Freshness — Short-lived JWTs (default 5s) prevent stale decisions
- Uniqueness — Nonce cache prevents replay attacks
- Binding — Principal, action, resource, context hash, and entity hash in the JWT prevent token reuse across different requests
License
MIT
