sentri
v5.2.2
Published
Auth/authorization library for Express, Fastify, Hono, and Elysia — PostgreSQL + JWT
Maintainers
Readme
Sentri
Sentri is a comprehensive, production-ready authentication and authorization library designed for modern Node.js ecosystems. It provides seamless integration with Express, Fastify, Hono, Elysia, and Koa. Sentri is engineered to handle complex identity requirements, including role-based access control (RBAC), attribute-based access control (ABAC), session management, idempotency, and multi-identifier login systems (e.g., email, phone number, username).
Sentri operates in two distinct modes to accommodate diverse architectural needs:
- Server Mode: Functions as a fully-featured, standalone authentication server. It leverages Kysely for automatic database schema management, issues standard JSON Web Tokens (JWTs), maintains active session state, and exposes out-of-the-box endpoints for user registration, login, token rotation, and public key discovery (SSO).
- Client Mode: Designed for stateless microservices that require robust validation of JWTs issued by the Sentri Server. Client Mode automatically fetches and caches public keys from the server, eliminating the need for direct database connectivity while maintaining strict security standards.
Supported Frameworks & Exports
| Import Path | Description |
| ---------------- | --------------------------------------------------------------------------- |
| sentri | Core functionality and framework-agnostic utilities. |
| sentri/core | Core types, configurations, SentriError, and SentriLogger. |
| sentri/express | Express.js adapter (createAuthExpress, middleware, routing). |
| sentri/fastify | Fastify adapter (createAuthFastify, hooks, and plugin). |
| sentri/hono | Hono adapter (createAuthHono, middleware, routing). |
| sentri/elysia | Elysia adapter (createAuthElysia, middleware, routing). |
| sentri/koa | Koa adapter (createAuthKoa, middleware, routing). |
Table of Contents
- Installation
- Quick Start Guides
- Architecture Overview
- Server Mode Configurations
- Client Mode Integrations
- Single Sign-On (SSO) Capabilities
- Multi-Identifier System
- Idempotency & Rate Limiting
- Middleware Reference
- REST Endpoints
- Error Handling & Customization
Installation
Install Sentri via NPM:
npm install sentriSentri bundles kysely natively, alleviating the need for separate query builder installations. However, you must install the specific driver corresponding to your relational database system.
PostgreSQL (Recommended):
npm install pgMySQL / SQLite:
npm install mysql2
npm install better-sqlite3Framework Peer Dependencies: Install the framework you intend to use Sentri with:
# Express
npm install express
# Fastify
npm install fastify @fastify/cookie
# Hono
npm install hono
# Elysia
npm install elysia
# Koa
npm install koa @koa/router koa-bodyparserQuick Start Guides
Express.js Integration (Server Mode)
import express from "express";
import { createAuthExpress } from "sentri/express";
import { PostgresDialect } from "kysely";
import pg from "pg";
const { Pool } = pg;
// 1. Initialize the Auth Server
const auth = createAuthExpress({
mode: "server",
validRoles: ["member", "administrator"] as const,
dialect: new PostgresDialect({
pool: new Pool({ connectionString: process.env.DATABASE_URL! }),
}),
});
const app = express();
app.use(express.json());
// 2. Apply Idempotency Middleware (Optional but recommended)
app.use(auth.idempotencyMiddleware());
// 3. Execute Database Migrations (Safe to run on every startup)
await auth.migrate();
// 4. Mount the Authentication Router
app.use("/auth", auth.router());
// 5. Implement Protected Routes
app.get("/api/profile", auth.protect(), (req, res) => {
res.json(req.user); // req.user is guaranteed to be populated here
});
// 6. Mount the Global Error Handler
app.use(auth.errorHandler());
app.listen(3000, () => console.log("Sentri Express Server is operational."));Fastify Integration (Server Mode)
import Fastify from "fastify";
import cookie from "@fastify/cookie";
import { createAuthFastify } from "sentri/fastify";
import { PostgresDialect } from "kysely";
import pg from "pg";
const { Pool } = pg;
const auth = createAuthFastify({
mode: "server",
validRoles: ["member", "administrator"] as const,
dialect: new PostgresDialect({
pool: new Pool({ connectionString: process.env.DATABASE_URL! }),
}),
});
await auth.migrate();
const app = Fastify();
await app.register(cookie);
// Register Sentri plugin encapsulating all endpoints
await app.register(auth.plugin(), { prefix: "/auth" });
app.setErrorHandler(auth.errorHandler());
app.get("/api/profile", { preHandler: auth.protect() }, async (request, reply) => {
reply.send(request.user);
});
await app.listen({ port: 3000 });Single Sign-On (SSO) Capabilities
Sentri facilitates seamless Single Sign-On across distributed systems. By leveraging asymmetric cryptography (RS256), the Central Authentication Server can distribute public keys dynamically to microservices.
The SSO Flow
- Authentication: The client authenticates via
POST /auth/loginon the Auth Server and receives a signed JWT access token. - Resource Request: The client requests a protected resource from an independent Microservice (operating in Sentri Client Mode) using the
Authorization: Bearer <token>header. - Stateless Verification: The Microservice fetches the public key from the Auth Server via
GET /auth/keys(cached automatically) and cryptographically verifies the token's validity and integrity without establishing a database connection.
Multi-Identifier System
Sentri discards the traditional single-identifier constraints. Users can register and authenticate using an arbitrary number of unique identifiers (e.g., email, phone number, corporate username).
All identifier values are enforced to be globally unique across the system, preventing account conflicts.
Bulk Identifier Management via API
Identifiers can be managed dynamically utilizing the built-in REST endpoints or the programmatic API.
// Add new identifiers to an existing user
await auth.bulkCreateIdentifiers(userId, [
{ type: "phone", value: "+1234567890" }
]);
// Update existing identifiers
await auth.bulkUpdateIdentifiers(userId, [
{ type: "email", value: "[email protected]" }
]);Idempotency & Rate Limiting
Request Idempotency
Sentri includes robust idempotency middleware to prevent duplicate processing of non-idempotent operations (e.g., POST, PUT, PATCH) arising from network retries.
Clients provide an X-Idempotency-Key header. Sentri captures the response and serves it instantaneously on subsequent requests sharing the identical key. This feature can leverage Redis for distributed environments.
// Enable idempotency globally or on specific routes
app.use(auth.idempotencyMiddleware({
ttl: 300_000, // Cache duration in milliseconds
redisUrl: process.env.REDIS_URL, // Optional: Distributed caching
}));Rate Limiting
Protect endpoints from brute-force attacks and abuse by implementing granular rate limiting.
app.post(
"/api/secure-action",
auth.rateLimiter({ maxAttempts: 5, durationSeconds: 60, keyPrefix: "secure-action" }),
actionHandler
);API Key Bypass (Machine-to-Machine)
Sentri natively supports authentication and authorization bypass via the X-Api-Key header for trusted server-to-server communication. When a valid API Key is provided (configured in AuthConfig via the apiKey property), the request will instantly bypass protect(), authorize(), and permit() checks without requiring a JWT token or active session.
// Configure the API Key during initialization
const auth = createAuthExpress({
// ...other options
apiKey: process.env.SENTRI_API_KEY // e.g. "sk_test_12345"
});Note: Requests using an API Key will not have a `req.user` object populated, but they will be fully permitted through guarded routes.
Middleware Reference
Sentri provides highly composable middleware functions to enforce security policies at the route level.
auth.protect(): Validates the JWT access token (via Bearer header or Cookie) and populates the request context with the authenticated user data. Initiates silent token rotation if applicable.auth.authorize(...roles): Implements Role-Based Access Control (RBAC). Rejects the request with a403 FORBIDDENif the authenticated user lacks at least one of the specified roles. Must be chained afterprotect().auth.permit(checkFunction | options): Enables Attribute-Based Access Control (ABAC) and custom validation logic.
Advanced Permission Checks (ABAC)
app.delete(
"/api/documents/:id",
auth.protect(),
auth.permit({
roles: ["administrator"], // Administrators bypass the custom check
check: async (req) => {
// Execute business logic to verify ownership
const document = await database.documents.findById(req.params.id);
return document?.ownerId === req.user!.id;
},
}),
deleteDocumentHandler
);REST Endpoints
When auth.router() (or equivalent) is mounted, Sentri exposes the following endpoints:
| HTTP Method | Route Endpoint | Authorization | Description |
| ----------- | ----------------------------- | -------------- | --------------------------------------------------------------------------- |
| GET | /keys | Public | Exposes the JWKS public key set (Requires RS256 configuration). |
| POST | /register | Public | Registers a new user. Supports X-Api-Key protection. |
| POST | /login | Public | Authenticates user and issues Access & Refresh Tokens. |
| POST | /refresh | Refresh Token | Rotates the refresh token and issues a new access token. |
| POST | /logout | Refresh Token | Terminates the current active session. |
| POST | /logout-all | Access Token | Revokes all active sessions across all devices for the user. |
| GET | /me | Access Token | Retrieves the comprehensive profile of the authenticated user. |
Note: Endpoints ending in
/identifiers(e.g.,PUT /me/identifiers,PATCH /users/:userId/identifiers) expect the request body to be a valid JSON Array ([...]), not an Object.
| GET | /me/identifiers | Access Token | Lists all registered identifiers for the authenticated user. |
| POST | /me/identifiers | Access Token | Registers additional identifiers in bulk. |
| PUT | /me/identifiers | Access Token | Modifies existing identifiers in bulk. |
| DELETE | /me/identifiers | Access Token | Removes identifiers in bulk (minimum one required). |
| PATCH | /me/password | Access Token | Updates the user's password and revokes all active sessions. |
| POST | /users/:userId/roles | Administrators | Assigns roles to a specified user. |
| DELETE | /users/:userId | API Key | Deletes a user completely, cascading to all sessions and identifiers. |
| PATCH | /users/:userId/identifiers | API Key | Admin: Modifies a user's existing identifiers in bulk. |
| PATCH | /users/:userId/password | API Key | Admin: Updates a user's password and revokes all active sessions. |
| POST | /bulk-register | API Key | Admin: Registers multiple users in bulk with configurable conflict handling.|
Error Handling & Customization
Sentri provides a universal Error Handler that transforms internal SentriError exceptions into standardized JSON responses complying with standard HTTP status codes.
app.use(auth.errorHandler({
onUnhandled: (error) => {
// Integrate with external logging services (e.g., Sentry, Datadog)
logger.error("Unhandled Sentri exception:", error);
}
}));Custom Exception Throwing
You may utilize the SentriError class to throw domain-specific errors that will be seamlessly caught and formatted by the Sentri Error Handler.
import { SentriError } from "sentri/core";
export class ResourceNotFoundError extends SentriError {
constructor(resourceName: string) {
super("NOT_FOUND", `The requested resource '${resourceName}' could not be located.`, 404);
}
}Sentri is maintained by rizzzdev and released under the ISC License.
