⚠️ Porta is in beta — APIs and features may change before v1.0
Skip to content

System Overview

Last Updated: 2026-04-25

High-Level Architecture

Porta is a multi-tenant OIDC identity provider built on Koa + node-oidc-provider + TypeScript. It serves as a complete authentication and user management platform with organization-scoped tenancy, RBAC, custom claims, and two-factor authentication.

Component Overview

Core Runtime

ComponentTechnologyPurpose
HTTP ServerKoa 2.xRequest handling, middleware pipeline
OIDC Enginenode-oidc-provider 9.xOpenID Connect protocol implementation
DatabasePostgreSQL 16 (pg)Persistent storage, long-lived OIDC artifacts
Cache/SessionsRedis 7 (ioredis)Short-lived OIDC artifacts, tenant cache, rate limits
LoggerpinoStructured logging (JSON in prod, pretty in dev)
ConfigzodEnvironment validation with fail-fast semantics
Signingjose + cryptoES256 (ECDSA P-256) token signing
CLIyargsAdmin command-line interface
Admin GUI SPAReact 19 + FluentUI v9Browser-based admin dashboard
Admin GUI BFFKoa (separate process)Backend-for-frontend: OIDC auth, session, API proxy
Admin GUI BuildViteSPA bundler and dev server

Domain Modules

Porta follows a modular domain architecture where each business domain is encapsulated in its own directory under src/:

ModuleDirectoryResponsibility
Organizationssrc/organizations/Tenant management, status lifecycle, branding
Applicationssrc/applications/SaaS product definitions, module grouping
Clientssrc/clients/OIDC client registration, secret management
Userssrc/users/User accounts, passwords, status lifecycle
Authsrc/auth/Authentication workflows, magic links, email, templates
RBACsrc/rbac/Roles, permissions, user-role assignments
Custom Claimssrc/custom-claims/Claim definitions, user claim values
Two-Factorsrc/two-factor/TOTP, email OTP, recovery codes
CLIsrc/cli/Admin CLI with dual-mode bootstrap

Each domain module follows a consistent internal structure:

src/<module>/
├── index.ts          # Barrel export (public API)
├── types.ts          # Domain types, interfaces, row mapping
├── errors.ts         # Domain-specific error classes
├── repository.ts     # PostgreSQL CRUD operations
├── cache.ts          # Redis caching layer
├── service.ts        # Business logic, validation, orchestration
├── slugs.ts          # Slug generation and validation (where applicable)
└── validators.ts     # Input validation (where applicable)

Admin GUI Module

The Admin GUI is a separate application in admin-gui/ with its own package.json, build pipeline, and test suite. It consists of two layers:

LayerTechnologyPurpose
React SPA (admin-gui/src/client/)React 19, FluentUI v9, React Router, React QueryBrowser-based admin dashboard
Koa BFF (admin-gui/src/server/)Koa, koa-session, ioredisOIDC auth, session management, CSRF, API proxy

The SPA communicates exclusively through the BFF — it never talks to the Porta Admin API directly. The BFF handles Bearer token injection, OIDC token refresh, and security concerns (CSRF, session cookies).

SPA Architecture:

admin-gui/src/client/
├── api/             # Typed API client + 13 React Query domain hook modules
├── components/      # 27 reusable UI components (EntityDataGrid, StatusBadge, etc.)
├── hooks/           # 8 hooks (useAuth, useOrgContext, useTheme, etc.)
├── layouts/         # AppShell, Sidebar, TopBar, Breadcrumbs
├── pages/           # Route page components (Dashboard, Orgs, Apps, Users, etc.)
├── router.tsx       # React Router with breadcrumb-enabled routes
└── types.ts         # Shared client-side type definitions

Application Startup Sequence

The application starts via src/index.ts in a strict 7-step sequence:

Fail-fast principle: If any startup step fails (DB connection, Redis connection, config validation), the process logs a fatal error and calls process.exit(1).

Middleware Stack

The Koa middleware stack is assembled in src/server.ts in this precise order:

Key Middleware Details

MiddlewareFilePurpose
Error Handlererror-handler.tsGlobal try/catch, hides internal details for 5xx
Request Loggerrequest-logger.tsUUID request ID, logs method/url/status/duration
Security Headerssecurity-headers.tsCSP default-src 'none', X-Frame-Options, HSTS, etc.
Metricsmetrics.tsPrometheus metrics at GET /metrics (optional)
Root Pageroot-page.tsNeutral /, /robots.txt, /favicon.ico (no product leakage)
Health Checkhealth.tsDB + Redis connectivity check at /health
Readinessready.tsReadiness probe for container orchestration
Admin Authadmin-auth.tsJWT Bearer validation for /api/admin/* routes
Admin CORSadmin-cors.tsCORS handling for /api/admin/* (configurable origins)
Admin Rate Limiteradmin-rate-limiter.tsRate limiting for admin API endpoints
Require Permissionrequire-permission.tsGranular RBAC permission checks for admin routes
Token Rate Limitertoken-rate-limiter.tsRate limiting for token endpoints
Tenant Resolvertenant-resolver.tsCache-first org lookup from URL slug
Client Secret Hashclient-secret-hash.tsSHA-256 pre-hash for client_secret_post
OIDC CORSoidc-cors.tsCORS handling for OIDC endpoints

Multi-Tenancy Model

Porta uses path-based multi-tenancy where each organization gets its own OIDC issuer URL:

https://auth.example.com/{orgSlug}/.well-known/openid-configuration
https://auth.example.com/{orgSlug}/auth
https://auth.example.com/{orgSlug}/token

The tenant resolver middleware (src/middleware/tenant-resolver.ts) uses a cache-first strategy:

  1. Check Redis cache for org by slug
  2. On miss → query PostgreSQL
  3. Cache the result in Redis
  4. Set ctx.state.organization for downstream handlers
  5. Return appropriate HTTP status based on org status:
    • active → proceed
    • suspended → 403
    • archived → 410
    • Not found → pass through (no match)

Graceful Shutdown

On SIGTERM or SIGINT:

  1. Stop accepting new connections
  2. Close the HTTP server
  3. Disconnect Redis
  4. Close the PostgreSQL pool
  5. Force exit after 10 seconds if still hanging

Released under the MIT License.