Authentication -- Usage & Examples
Securing routes, authentication flows, JWKS microservice patterns, entity helpers, and API endpoint specifications. See Setup & Configuration for initial setup.
Securing Routes
Use the authenticate field in route configurations. The field accepts TRouteAuthenticateConfig:
// Single strategy
const SECURE_ROUTE_CONFIG = {
path: '/secure-data',
method: HTTP.Methods.GET,
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
responses: jsonResponse({
description: 'Protected data',
schema: z.object({ message: z.string() }),
}),
} as const;
// Multiple strategies with fallback (any mode)
const FALLBACK_AUTH_CONFIG = {
path: '/api/data',
method: HTTP.Methods.GET,
authenticate: {
strategies: [Authentication.STRATEGY_JWT, Authentication.STRATEGY_BASIC],
mode: AuthenticationModes.ANY,
},
responses: jsonResponse({
description: 'Data accessible via JWT or Basic auth',
schema: z.object({ data: z.any() }),
}),
} as const;
// Skip authentication
const PUBLIC_ROUTE_CONFIG = {
path: '/public',
method: HTTP.Methods.GET,
authenticate: { skip: true },
responses: jsonResponse({
description: 'Public endpoint',
schema: z.object({ message: z.string() }),
}),
} as const;Using the authenticate() Standalone Function
The authenticate() function creates an AuthenticationProvider instance and uses its middleware factory. It returns a Hono MiddlewareHandler suitable for direct middleware usage:
import { authenticate, Authentication, AuthenticationModes } from '@venizia/ignis';
// Use as Hono middleware directly
const authMiddleware = authenticate({
strategies: [Authentication.STRATEGY_JWT],
mode: AuthenticationModes.ANY,
});
// Apply to a Hono route
app.get('/protected', authMiddleware, (c) => {
const user = c.get(Authentication.CURRENT_USER);
return c.json({ userId: user.userId });
});Accessing the Current User
After authentication, the user payload is available on the Hono Context:
import { Context } from 'hono';
import { Authentication, IJWTTokenPayload } from '@venizia/ignis';
// Inside a route handler
const user = c.get(Authentication.CURRENT_USER) as IJWTTokenPayload | undefined;
if (user) {
console.log('Authenticated user ID:', user.userId);
console.log('User roles:', user.roles);
}Dynamic Skip Authentication
Use Authentication.SKIP_AUTHENTICATION to dynamically skip auth in middleware:
import { Authentication } from '@venizia/ignis';
import { createMiddleware } from 'hono/factory';
const conditionalAuthMiddleware = createMiddleware(async (c, next) => {
if (c.req.header('X-API-Key') === 'valid-api-key') {
c.set(Authentication.SKIP_AUTHENTICATION, true);
}
return next();
});Implementing an AuthenticationService
The AuthenticateComponent depends on a service implementing the IAuthService interface when using the built-in auth controller.
JWS Example
import {
BaseService,
inject,
IAuthService,
IJWTTokenPayload,
JWSTokenService,
BindingKeys,
BindingNamespaces,
TSignInRequest,
TContext,
} from '@venizia/ignis';
import { getError } from '@venizia/ignis-helpers';
import { Env } from 'hono';
export class AuthenticationService extends BaseService implements IAuthService {
constructor(
@inject({
key: BindingKeys.build({
namespace: BindingNamespaces.SERVICE,
key: JWSTokenService.name,
}),
})
private _tokenService: JWSTokenService,
) {
super({ scope: AuthenticationService.name });
}
async signIn(context: TContext<Env>, opts: TSignInRequest): Promise<{ token: string }> {
const { identifier, credential } = opts;
const user = await this.userRepo.findByIdentifier(identifier);
if (!user || !await this.verifyCredential(credential, user)) {
throw getError({ message: 'Invalid credentials' });
}
const payload: IJWTTokenPayload = {
userId: user.id,
roles: user.roles,
};
const token = await this._tokenService.generate({ payload });
return { token };
}
async signUp(context: TContext<Env>, opts: any): Promise<any> {
// Implement your sign-up logic
}
async changePassword(context: TContext<Env>, opts: any): Promise<any> {
// Implement your change password logic
}
}JWKS Issuer Example
import {
BaseService,
inject,
IAuthService,
IJWTTokenPayload,
JWKSIssuerTokenService,
BindingKeys,
BindingNamespaces,
TSignInRequest,
TContext,
} from '@venizia/ignis';
import { getError } from '@venizia/ignis-helpers';
import { Env } from 'hono';
export class AuthenticationService extends BaseService implements IAuthService {
constructor(
@inject({
key: BindingKeys.build({
namespace: BindingNamespaces.SERVICE,
key: JWKSIssuerTokenService.name,
}),
})
private _tokenService: JWKSIssuerTokenService,
) {
super({ scope: AuthenticationService.name });
}
async signIn(context: TContext<Env>, opts: TSignInRequest): Promise<{ token: string }> {
const { identifier, credential } = opts;
// ... lookup and verify user ...
const payload: IJWTTokenPayload = {
userId: user.id,
roles: user.roles,
};
const token = await this._tokenService.generate({ payload });
return { token };
}
// ... signUp, changePassword ...
}JWKS Microservice Patterns
Issuer + Verifier Architecture
In a microservice architecture, one service issues tokens (issuer) and other services verify them (verifier):
Auth Service (Issuer):
this.bind<TJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
standard: JOSEStandards.JWKS,
options: {
mode: JWKSModes.ISSUER,
algorithm: 'ES256',
keys: {
driver: JWKSKeyDrivers.FILE,
format: JWKSKeyFormats.PEM,
private: './keys/private.pem',
public: './keys/public.pem',
},
kid: 'auth-key-1',
getTokenExpiresFn: () => 86400,
},
});API Service (Verifier):
this.bind<TJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
standard: JOSEStandards.JWKS,
options: {
mode: JWKSModes.VERIFIER,
jwksUrl: 'https://auth-service.internal/certs',
cacheTtlMs: 43_200_000, // Cache for 12 hours
cooldownMs: 30_000, // Min 30s between refreshes
},
});JWKS with AES Payload Encryption
When using AES payload encryption across services, both issuer and verifier must share the same applicationSecret:
Issuer:
{
mode: JWKSModes.ISSUER,
algorithm: 'ES256',
keys: { /* ... */ },
kid: 'auth-key-1',
getTokenExpiresFn: () => 86400,
applicationSecret: process.env.APP_ENV_APPLICATION_SECRET,
}Verifier:
{
mode: JWKSModes.VERIFIER,
jwksUrl: 'https://auth-service.internal/certs',
applicationSecret: process.env.APP_ENV_APPLICATION_SECRET, // Must match issuer
}JWKS Key Generation
Generate ES256 keys for JWKS:
# Generate private key
openssl ecparam -genkey -name prime256v1 -noout -out private.pem
# Generate public key from private key
openssl ec -in private.pem -pubout -out public.pemGenerate RS256 keys:
# Generate private key
openssl genrsa -out private.pem 2048
# Generate public key from private key
openssl rsa -in private.pem -pubout -out public.pemWARNING
Never commit private keys to version control. The .gitignore includes patterns for *.pem, *.key, and keys/ directories.
Inline Keys (Text Driver)
For environments where file access is restricted (e.g., serverless), use the text driver:
{
mode: JWKSModes.ISSUER,
algorithm: 'ES256',
keys: {
driver: JWKSKeyDrivers.TEXT,
format: JWKSKeyFormats.PEM,
private: process.env.JWKS_PRIVATE_KEY!, // PEM string from env
public: process.env.JWKS_PUBLIC_KEY!, // PEM string from env
},
kid: 'auth-key-1',
getTokenExpiresFn: () => 86400,
}Entity Column Helpers
The authentication module provides a set of column helper functions designed to be spread into Drizzle pgTable() definitions. These functions return pre-configured column objects for common auth-related entities, saving you from manually defining columns for users, roles, permissions, and their relationships.
Pattern
Each helper function returns an object of Drizzle column builders that you spread into your pgTable() call alongside any custom columns:
import { pgTable, serial, text } from 'drizzle-orm/pg-core';
import {
extraUserColumns,
extraRoleColumns,
extraPermissionColumns,
extraPolicyDefinitionColumns,
} from '@venizia/ignis';
import { withSerialId, withTimestamps } from '@venizia/ignis';
// User table with auth columns
export const users = pgTable('users', {
...withSerialId(),
...withTimestamps(),
...extraUserColumns(),
username: text('username').unique().notNull(),
passwordHash: text('password_hash').notNull(),
email: text('email').unique(),
});
// Role table with auth columns
export const roles = pgTable('roles', {
...withSerialId(),
...withTimestamps(),
...extraRoleColumns(),
});
// Permission table
export const permissions = pgTable('permissions', {
...withSerialId(),
...withTimestamps(),
...extraPermissionColumns(),
});
// Policy definition table (Casbin-style policies)
export const policyDefinitions = pgTable('policy_definitions', {
...withSerialId(),
...withTimestamps(),
...extraPolicyDefinitionColumns(),
});extraUserColumns
Returns columns for user-related fields with status and type defaults from UserStatuses and UserTypes.
extraUserColumns(opts?: { idType: 'string' | 'number' })| Column | Type | Default | Description |
|---|---|---|---|
realm | text | '' | Multi-tenancy realm identifier |
status | text | UserStatuses.UNKNOWN ('000_UNKNOWN') | User status |
type | text | UserTypes.SYSTEM ('SYSTEM') | User type |
activatedAt | timestamp (tz) | null | Activation timestamp |
lastLoginAt | timestamp (tz) | null | Last login timestamp |
parentId | text or integer | null | Parent user ID (type depends on idType) |
extraRoleColumns
Returns columns for role definitions. No options parameter.
extraRoleColumns()| Column | Type | Default | Description |
|---|---|---|---|
identifier | text | -- | Unique role identifier (e.g., 'admin', 'user') |
name | text | -- | Human-readable role name |
description | text | null | Optional role description |
priority | integer | -- | Role priority (lower = higher priority) |
status | text | RoleStatuses.ACTIVATED ('201_ACTIVATED') | Role status |
extraPermissionColumns
Returns columns for permission definitions. Supports idType option for the parentId column type.
extraPermissionColumns(opts?: { idType: 'string' | 'number' })| Column | Type | Default | Description |
|---|---|---|---|
code | text (unique) | -- | Unique permission code |
name | text | -- | Permission name |
subject | text | -- | Permission subject (e.g., 'User', 'Order') |
action | text | -- | Permitted action (e.g., 'read', 'write') |
scope | text | -- | Permission scope |
parentId | text or integer | null | Parent permission ID |
extraPolicyDefinitionColumns
Returns columns for Casbin-style policy definitions that map subjects (users/roles) to targets (resources/permissions).
extraPolicyDefinitionColumns(opts?: { idType: 'string' | 'number' })| Column | DB Column | Type | Nullable | Default | Description |
|---|---|---|---|---|---|
variant | variant | text | No | -- | Policy variant (e.g., 'p' for policy, 'g' for grouping) |
subjectType | subject_type | text | No | -- | Type of subject (e.g., 'user', 'role') |
targetType | target_type | text | No | -- | Type of target (e.g., 'permission', 'role') |
action | action | text | Yes | null | Policy action |
effect | effect | text | Yes | null | Policy effect (e.g., 'allow', 'deny') |
domain | domain | text | Yes | null | Policy domain for multi-tenancy |
subjectId | subject_id | text or integer | No | -- | Subject ID (type depends on idType) |
targetId | target_id | text or integer | No | -- | Target ID (type depends on idType) |
ID Type Polymorphism
All column helpers that accept opts.idType default to 'number' (producing integer columns). Pass 'string' to use text columns instead:
// Number IDs (default) -- uses integer columns for FK references
extraUserColumns()
extraPermissionColumns()
// String IDs (e.g., UUID) -- uses text columns for FK references
extraUserColumns({ idType: 'string' })
extraPermissionColumns({ idType: 'string' })Status Constants
The authentication module uses status classes from @/common/statuses. These extend CommonStatuses and provide lifecycle state management for auth entities.
UserStatuses
Inherits all statuses from CommonStatuses:
| Constant | Value | Description |
|---|---|---|
UserStatuses.UNKNOWN | '000_UNKNOWN' | Initial/unverified state |
UserStatuses.ACTIVATED | '201_ACTIVATED' | Active user |
UserStatuses.DEACTIVATED | '401_DEACTIVATED' | Deactivated user |
UserStatuses.BLOCKED | '403_BLOCKED' | Blocked user |
UserStatuses.ARCHIVED | '405_ARCHIVED' | Archived user |
UserTypes
| Constant | Value | Description |
|---|---|---|
UserTypes.SYSTEM | 'SYSTEM' | System-created user (default) |
UserTypes.LINKED | 'LINKED' | Linked/external user |
RoleStatuses
Inherits all statuses from CommonStatuses (same values as UserStatuses):
| Constant | Value | Description |
|---|---|---|
RoleStatuses.UNKNOWN | '000_UNKNOWN' | Initial state |
RoleStatuses.ACTIVATED | '201_ACTIVATED' | Active role (default for extraRoleColumns) |
RoleStatuses.DEACTIVATED | '401_DEACTIVATED' | Deactivated role |
RoleStatuses.BLOCKED | '403_BLOCKED' | Blocked role |
RoleStatuses.ARCHIVED | '405_ARCHIVED' | Archived role |
Auth Flows
JWS Authentication Flow
- Client sends request with
Authorization: Bearer <token>header - JWSAuthenticationStrategy.authenticate() is called by the Hono middleware
- AbstractBearerTokenService.extractCredentials() extracts the token from the Authorization header
- JWSTokenService.doVerify() verifies the JWT signature using
jose.jwtVerify()with the sharedjwtSecret - AbstractBearerTokenService.decryptPayload() decrypts the AES-encrypted payload fields (if AES configured)
- User payload is set on
context.get(Authentication.CURRENT_USER)
JWKS Issuer Authentication Flow
- Client sends request with
Authorization: Bearer <token>header - JWKSIssuerAuthenticationStrategy.authenticate() is called by the Hono middleware
- AbstractBearerTokenService.extractCredentials() extracts the token from the Authorization header
- JWKSIssuerTokenService.doVerify() calls
ensureInitialized()(lazy-loads keys on first call), then verifies the JWT using the public key - AbstractBearerTokenService.decryptPayload() decrypts the AES-encrypted payload fields (if AES configured)
- User payload is set on
context.get(Authentication.CURRENT_USER)
JWKS Verifier Authentication Flow
- Client sends request with
Authorization: Bearer <token>header - JWKSVerifierAuthenticationStrategy.authenticate() is called by the Hono middleware
- AbstractBearerTokenService.extractCredentials() extracts the token from the Authorization header
- JWKSVerifierTokenService.doVerify() calls
ensureInitialized()(creates remote JWKS verifier on first call), then verifies the JWT using the remote JWKS - AbstractBearerTokenService.decryptPayload() decrypts the AES-encrypted payload fields (if AES configured)
- User payload is set on
context.get(Authentication.CURRENT_USER)
Basic Authentication Flow
- Client sends request with
Authorization: Basic <base64(username:password)>header - BasicAuthenticationStrategy.authenticate() is called by the Hono middleware
- BasicTokenService.extractCredentials() decodes the Base64 credentials
- BasicTokenService.verify() calls the user-provided
verifyCredentialscallback with{ credentials, context } - User payload is set on
context.get(Authentication.CURRENT_USER)if verification succeeds
IMPORTANT
The verifyCredentials callback must perform all necessary validation (password hashing comparison, user lookup, etc.) and return an IAuthUser object or null.
Multi-Strategy Authentication
When multiple strategies are configured on a route via authenticate: { strategies: ['jwt', 'basic'] }:
any mode (default):
- Strategies are tried in the order specified
- The first successful strategy wins
- Errors from failing strategies are discarded (logged at debug level)
- If all strategies fail, a
401 Unauthorizederror is thrown listing all tried strategies - Use case: Fallback authentication (try JWT, fallback to Basic)
all mode:
- Every strategy must pass successfully
- If any strategy fails, the request is immediately rejected (exception propagates)
- The first strategy's user payload is used as the identity source
- Use case: Multi-factor authentication (both JWT and Basic required)
TIP
Use 'any' mode for graceful fallback (e.g., allow mobile apps to use JWT while legacy systems use Basic). Use 'all' mode for high-security endpoints requiring multiple forms of authentication.
Token Encryption (Optional AES)
JWT payloads can optionally be encrypted field-by-field using AES (default aes-256-cbc) via the @venizia/ignis-helpers AES utility. This is configured by providing applicationSecret in the service options.
NOTE
AES payload encryption is optional for all JOSE standards (JWS and JWKS). When applicationSecret is not provided, payloads are stored in standard plaintext JWT format.
Encryption process (when applicationSecret is provided):
- Standard JWT fields (
iss,sub,aud,jti,nbf,exp,iat) are preserved as-is - All other fields have both their keys and values AES-encrypted
- The
rolesfield is serialized asid|identifier|prioritypipe-separated strings before encryption nullandundefinedvalues are skipped during encryption
Decryption process:
- If AES is not configured (
this.aesis null), the payload is returned as-is - Standard JWT fields are extracted directly
- Encrypted fields have their keys decrypted first, then their values
- The
rolesfield is deserialized: JSON-parsed to a string array, then each entry is split on|to reconstruct objects withid,identifier, andpriority(wherepriorityis converted to integer viaint())
WARNING
The applicationSecret must remain constant across all instances of your application. Changing it will invalidate all existing tokens, as they cannot be decrypted with a different secret. In JWKS microservice setups, the issuer and all verifiers must share the same applicationSecret.
Hono Context Extension
The Authentication module extends Hono's ContextVariableMap to provide type-safe access to auth data. Note: ContextVariableMap does not take a generic parameter — it is a plain interface augmentation:
declare module 'hono' {
interface ContextVariableMap {
[Authentication.CURRENT_USER]: IAuthUser;
[Authentication.AUDIT_USER_ID]: IdType;
}
}This enables type-safe access in route handlers:
// TypeScript knows this is IAuthUser
const user = c.get(Authentication.CURRENT_USER);Context variable keys (from Authentication constants):
| Key | Constant | Type | Description |
|---|---|---|---|
'auth.current.user' | Authentication.CURRENT_USER | IAuthUser | The authenticated user payload |
'audit.user.id' | Authentication.AUDIT_USER_ID | IdType | The authenticated user's ID (extracted from userId) |
'authentication.skip' | Authentication.SKIP_AUTHENTICATION | boolean | Set to true to bypass authentication on a request |
Request Schemas
SignInRequestSchema
The built-in schema uses a nested identifier + credential structure:
const SignInRequestSchema = z.object({
identifier: z.object({
scheme: requiredString({ min: 4 }), // e.g., 'username', 'email'
value: requiredString({ min: 8 }), // the actual identifier value
}),
credential: z.object({
scheme: requiredString(), // e.g., 'basic', 'password'
value: requiredString({ min: 8 }), // the actual credential value
}),
clientId: z.string().optional(), // optional auth provider
});
type TSignInRequest = z.infer<typeof SignInRequestSchema>;SignUpRequestSchema
The built-in schema uses a flat structure:
const SignUpRequestSchema = z.object({
username: z.string().nonempty().min(8),
credential: z.string().nonempty().min(8),
});
type TSignUpRequest = z.infer<typeof SignUpRequestSchema>;ChangePasswordRequestSchema
const ChangePasswordRequestSchema = z.object({
scheme: z.string(),
oldCredential: requiredString({ min: 8 }),
newCredential: requiredString({ min: 8 }),
userId: z.string().or(z.number()),
});
type TChangePasswordRequest = z.infer<typeof ChangePasswordRequestSchema>;JWTTokenPayloadSchema
Exported from the controller factory module. Used as the response schema for the /who-am-i endpoint:
const JWTTokenPayloadSchema = z.object({
userId: z.string().or(z.number()),
roles: z.array(
z.object({
id: z.string().or(z.number()),
identifier: z.string(),
priority: z.number().int(),
}),
),
clientId: z.string().optional(),
provider: z.string().optional(),
email: z.email().optional(),
});API Endpoints
The built-in auth controller is created by the defineAuthController() factory function and is only available when useAuthController: true is set in REST_OPTIONS.
| Method | Path | Auth Required | Description |
|---|---|---|---|
POST | /auth/sign-in | No | Authenticate and receive a JWT token |
POST | /auth/sign-up | Configurable | Create a new user account |
POST | /auth/change-password | JWT | Change the authenticated user's password |
GET | /auth/who-am-i | JWT | Return the current user's JWT payload |
GET | /certs | No | JWKS endpoint (JWKS Issuer mode only) |
NOTE
The base path /auth is configurable via controllerOpts.restPath. The /certs path is configurable via rest.path in IJWKSIssuerOptions. The /certs endpoint is intentionally unauthenticated — it serves the public keys needed by external verifiers.
POST /auth/sign-in
Authentication: None
Request Body:
Uses SignInRequestSchema by default, or a custom schema via payload.signIn.request.schema.
Response 200:
Uses payload.signIn.response.schema if provided, otherwise AnyObjectSchema.
{
"token": "eyJhbGciOiJFUzI1NiIsImtpZCI6Im15LWtleS1pZC0xIn0..."
}POST /auth/sign-up
Authentication: Configurable via requireAuthenticatedSignUp (default: false)
When requireAuthenticatedSignUp: true, requires JWT authentication. When false, the endpoint is public.
POST /auth/change-password
Authentication: Always requires JWT (Authentication.STRATEGY_JWT)
GET /auth/who-am-i
Authentication: Always requires JWT (Authentication.STRATEGY_JWT)
Returns the current user's decrypted JWT payload directly from context.
{
"userId": "123",
"roles": [
{ "id": "1", "identifier": "admin", "priority": 0 }
],
"clientId": "optional-client-id",
"provider": "optional-provider",
"email": "user@example.com"
}GET /certs (JWKS Issuer Only)
Authentication: None (intentionally public)
Returns the JSON Web Key Set for external verifiers.
{
"keys": [
{
"kty": "EC",
"kid": "my-key-id-1",
"use": "sig",
"alg": "ES256",
"crv": "P-256",
"x": "...",
"y": "..."
}
]
}Cache headers: Cache-Control: public, max-age=3600, stale-while-revalidate=86400
Auth Entity Column Helpers
Ignis provides column helper functions that return pre-configured Drizzle column objects for common auth-related database tables. These functions are designed to be spread into pgTable() definitions, giving you standardized columns for User, Role, Permission, and PolicyDefinition entities without manually defining each column.
All helpers that accept an opts parameter support { idType: 'string' | 'number' } to control whether foreign key columns use text (for UUIDs) or integer (for serial IDs). The default is 'number'.
extraUserColumns
Import: import { extraUserColumns } from '@venizia/ignis';
Signature: extraUserColumns(opts?: { idType: 'string' | 'number' })
| Column | DB Column | Type | Nullable | Default | Description |
|---|---|---|---|---|---|
realm | realm | text | Yes | '' | Multi-tenancy realm identifier |
status | status | text | No | UserStatuses.UNKNOWN | User lifecycle status |
type | type | text | No | UserTypes.SYSTEM | User type (SYSTEM or LINKED) |
activatedAt | activated_at | timestamp (tz) | Yes | null | When the user was activated |
lastLoginAt | last_login_at | timestamp (tz) | Yes | null | Last login timestamp |
parentId | parent_id | text or integer | Yes | null | Parent user ID (type depends on idType) |
extraRoleColumns
Import: import { extraRoleColumns } from '@venizia/ignis';
Signature: extraRoleColumns()
| Column | DB Column | Type | Nullable | Default | Description |
|---|---|---|---|---|---|
identifier | identifier | text (unique) | No | -- | Unique role identifier (e.g., 'admin', 'editor') |
name | name | text | No | -- | Human-readable role name |
description | description | text | Yes | null | Optional role description |
priority | priority | integer | No | -- | Role priority (lower = higher priority) |
status | status | text | No | RoleStatuses.ACTIVATED | Role lifecycle status |
extraPermissionColumns
Import: import { extraPermissionColumns } from '@venizia/ignis';
Signature: extraPermissionColumns(opts?: { idType: 'string' | 'number' })
| Column | DB Column | Type | Nullable | Default | Description |
|---|---|---|---|---|---|
code | code | text (unique) | No | -- | Unique permission code |
name | name | text | No | -- | Permission display name |
subject | subject | text | No | -- | Permission subject (e.g., 'User', 'Order') |
action | action | text | No | -- | Permitted action (e.g., 'read', 'write') |
scope | scope | text | No | -- | Permission scope |
parentId | parent_id | text or integer | Yes | null | Parent permission ID (type depends on idType) |
extraPolicyDefinitionColumns
Import: import { extraPolicyDefinitionColumns } from '@venizia/ignis';
Signature: extraPolicyDefinitionColumns(opts?: { idType: 'string' | 'number' })
Provides columns for Casbin-style policy definitions that map subjects (users/roles) to targets (resources/permissions).
| Column | DB Column | Type | Nullable | Default | Description |
|---|---|---|---|---|---|
variant | variant | text | No | -- | Policy variant (e.g., 'p' for policy, 'g' for grouping) |
subjectType | subject_type | text | No | -- | Type of subject (e.g., 'user', 'role') |
targetType | target_type | text | No | -- | Type of target (e.g., 'permission', 'role') |
action | action | text | Yes | null | Policy action |
effect | effect | text | Yes | null | Policy effect (e.g., 'allow', 'deny') |
domain | domain | text | Yes | null | Policy domain for multi-tenancy |
subjectId | subject_id | text or integer | No | -- | Subject ID (type depends on idType) |
targetId | target_id | text or integer | No | -- | Target ID (type depends on idType) |
Usage Example
import { pgTable, serial, text } from 'drizzle-orm/pg-core';
import {
extraUserColumns,
extraRoleColumns,
extraPermissionColumns,
extraPolicyDefinitionColumns,
} from '@venizia/ignis';
import { withSerialId, withTimestamps } from '@venizia/ignis';
// User table
export const users = pgTable('users', {
...withSerialId(),
...withTimestamps(),
...extraUserColumns(),
username: text('username').unique().notNull(),
passwordHash: text('password_hash').notNull(),
email: text('email').unique(),
});
// Role table
export const roles = pgTable('roles', {
...withSerialId(),
...withTimestamps(),
...extraRoleColumns(),
});
// Permission table
export const permissions = pgTable('permissions', {
...withSerialId(),
...withTimestamps(),
...extraPermissionColumns(),
});
// Policy definition table (Casbin-style policies)
export const policyDefinitions = pgTable('policy_definitions', {
...withSerialId(),
...withTimestamps(),
...extraPolicyDefinitionColumns(),
});
// With UUID-based IDs
export const uuidUsers = pgTable('users', {
...withUuidId(),
...withTimestamps(),
...extraUserColumns({ idType: 'string' }),
username: text('username').unique().notNull(),
});
export const uuidPolicies = pgTable('policy_definitions', {
...withUuidId(),
...withTimestamps(),
...extraPolicyDefinitionColumns({ idType: 'string' }),
});Context Variables
The auth middleware sets several variables on the Hono Context object during request processing. These are declared via a ContextVariableMap module augmentation and can be accessed with c.get() / c.set().
| Constant | Key String | Type | Description |
|---|---|---|---|
Authentication.CURRENT_USER | 'auth.current.user' | IAuthUser | The authenticated user payload, set after successful authentication |
Authentication.AUDIT_USER_ID | 'audit.user.id' | IdType | The authenticated user's ID, extracted from the user payload |
Authentication.SKIP_AUTHENTICATION | 'authentication.skip' | boolean | Set to true in a preceding middleware to bypass authentication for the current request |
Authorization.RULES | 'authorization.rules' | unknown | Authorization rules resolved for the current request |
Authorization.SKIP_AUTHORIZATION | 'authorization.skip' | boolean | Set to true to bypass authorization checks for the current request |
Reading context variables in a handler:
import { Authentication, Authorization } from '@venizia/ignis';
// Inside a route handler
const currentUser = c.get(Authentication.CURRENT_USER);
const userId = c.get(Authentication.AUDIT_USER_ID);
const skipAuth = c.get(Authentication.SKIP_AUTHENTICATION);
const authzRules = c.get(Authorization.RULES);Skipping auth dynamically from middleware:
import { Authentication, Authorization } from '@venizia/ignis';
import { createMiddleware } from 'hono/factory';
const apiKeyMiddleware = createMiddleware(async (c, next) => {
if (c.req.header('X-API-Key') === process.env.INTERNAL_API_KEY) {
c.set(Authentication.SKIP_AUTHENTICATION, true);
c.set(Authorization.SKIP_AUTHORIZATION, true);
}
return next();
});See Also
- Setup & Configuration -- Binding keys, options interfaces, and initial setup
- API Reference -- Architecture, service internals, and strategy registry
- Error Reference -- Error messages and troubleshooting