Skip to content

Lifecycle & Context ​

Version 0.2 added expiration policy, audit-safe lifecycle hooks, and stable helpers for reading or forwarding verified key context.

TTL policy ​

typescript
ApiKeysModule.forRoot({
  namespace: 'acme',
  peppers: { 1: process.env.API_KEY_PEPPER! },
  storage,
  ttlPolicy: {
    defaultExpiresInMs: 90 * 24 * 60 * 60 * 1000,
    maxExpiresInMs: 365 * 24 * 60 * 60 * 1000,
    allowNeverExpires: false,
  },
});
OptionEffect
defaultExpiresInMsAssigns an expiry when create() does not provide one.
maxExpiresInMsRejects an expiry beyond the permitted lifetime.
allowNeverExpiresWhen false, creation or rotation cannot resolve to expiresAt: null.

An explicit expiresAt still passes through the policy. Replacement keys inherit the old expiry unless rotation overrides it.

Lifecycle events ​

typescript
ApiKeysModule.forRoot({
  namespace: 'acme',
  peppers: { 1: process.env.API_KEY_PEPPER! },
  storage,
  emitUsageEvents: false,
  onEvent: async (event) => {
    await auditLog.record(event);
  },
  onEventError: (error, event) => {
    logger.warn({ error, eventType: event.type }, 'API key event sink failed');
  },
});

The sink can receive:

EventEmitted when
api_key.createdA new key record is inserted.
api_key.revokedAn existing key is revoked.
api_key.rotatedA replacement and grace deadline are committed.
api_key.auth_failedCredential parsing or verification fails.
api_key.usedCredential-only verification or full request authorization succeeds and emitUsageEvents is enabled.
api_key.authorization_deniedA verified credential fails request environment, IP, or scope policy.

Raw keys, hashes, and pepper values are never included. Event sink failures are isolated from key operations; report them through onEventError and monitor that path independently.

api_key.used is disabled by default because it can be high volume. Prefer verification metrics for aggregate success and latency signals.

Read verified context in controllers ​

typescript
import {
  ApiKeyContext,
  ApiKeysGuard,
  CurrentApiKey,
} from '@nestarc/api-keys';

@UseGuards(ApiKeysGuard)
@Get()
list(@CurrentApiKey() apiKey: ApiKeyContext) {
  return this.reports.listForTenant(apiKey.tenantId);
}

ApiKeyContext contains:

  • keyId and the safe lookup prefix;
  • tenantId and environment;
  • flattened scopes;
  • the normalized allowedIpCidrs array.

For framework-level code, use getApiKeyContext(request). The stable request property name is exported as API_KEY_CONTEXT_PROPERTY and currently resolves to apiKey.

Bridge to tenancy or request-local storage ​

contextWriter runs only after environment, IP, and scope checks pass:

typescript
ApiKeysModule.forRoot({
  namespace: 'acme',
  peppers: { 1: process.env.API_KEY_PEPPER! },
  storage,
  contextWriter: (apiKey, request) => {
    authenticatedMachineContext.write(request, {
      tenantId: apiKey.tenantId,
      apiKeyId: apiKey.keyId,
    });
  },
});

This is the right boundary for tenancy, RLS, or correlation infrastructure that must only see an authorized machine identity.

RBAC composition ​

@nestarc/rbac can map this context to an api_key subject:

typescript
import { RbacModule } from '@nestarc/rbac';
import { createApiKeySubjectResolver } from '@nestarc/rbac/integrations/api-keys';

RbacModule.forRoot({
  storage: rbacStorage,
  subjectResolver: createApiKeySubjectResolver(),
  tenant: { requiredByDefault: true },
});

Run ApiKeysGuard before RbacGuard. API key scopes limit capabilities embedded in the credential; RBAC permissions evaluate role bindings for the key id. When both are required, both must pass.

Detached observer values ​

In 0.4, stored dates, operation results, lifecycle/metric payloads, and verified context are defensively copied at public boundaries. contextWriter receives an isolated copy and cannot replace the authenticated request.apiKey that reaches downstream guards. Denied request authorization produces no usage timestamp update or successful usage event. These observers remain best-effort hooks after operations, not a transactional audit log.

Released under the MIT License.