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

Admin GUI

The Porta Admin GUI is a web-based administration console for managing your Porta deployment. It provides a React-based single-page application (SPA) served through a Koa Backend-for-Frontend (BFF) that handles authentication, session management, and API proxying.

Current State

The Admin GUI web interface is currently a placeholder. The BFF server is fully functional with OIDC authentication, session management, CSRF protection, and API proxying. The full admin dashboard SPA is under active development.

For full administration capabilities, use the Porta CLI (porta command).

Architecture

┌─────────────┐     ┌──────────────────┐     ┌───────────────┐
│   Browser    │────▶│  Admin GUI BFF   │────▶│  Porta Server │
│  (React SPA) │◀────│  (Koa, port 4002)│◀────│  (port 3000)  │
└─────────────┘     └──────────────────┘     └───────────────┘


                    ┌──────────────┐
                    │    Redis     │
                    │  (sessions)  │
                    └──────────────┘
  • React SPA — FluentUI v9 placeholder (full dashboard under development)
  • BFF Server — Koa application that handles OIDC authentication, manages sessions in Redis, and proxies API requests to the Porta server
  • Session Store — Redis-backed sessions with configurable TTL

Prerequisites

  • A running Porta server (port 3000 by default)
  • Redis (shared with or separate from the Porta server)
  • An OIDC client registered for the admin GUI (created automatically by porta init)

Setup

When you run porta init, it automatically creates an "Admin GUI" confidential OIDC client with:

  • Client type: Confidential (with client secret)
  • Grant types: authorization_code, refresh_token
  • Redirect URI: http://localhost:4002/auth/callback
  • Login method: magic_link (passwordless)
  • Token endpoint auth: client_secret_post

The client ID and secret are displayed in the init summary. Save the secret — it cannot be retrieved later.

Environment Variables

Configure the admin GUI using these environment variables:

VariableRequiredDefaultDescription
PORTA_ADMIN_PORTNo4002Port for the admin GUI BFF server
PORTA_ADMIN_PORTA_URLYesURL of the Porta server (e.g., http://localhost:3000)
PORTA_ADMIN_CLIENT_IDYesOIDC client ID (from porta init)
PORTA_ADMIN_CLIENT_SECRETYesOIDC client secret (from porta init)
PORTA_ADMIN_SESSION_SECRETYesSecret for signing session cookies (min 32 chars)
PORTA_ADMIN_PUBLIC_URLNohttp://localhost:4002Public-facing URL of the admin GUI
PORTA_ADMIN_ORG_SLUGNoAuto-detectedOrganization slug for OIDC discovery
PORTA_ADMIN_SESSION_TTLNo3600Session duration in seconds
REDIS_URLYesRedis connection string (e.g., redis://localhost:6379/1)
NODE_ENVNodevelopmentEnvironment mode
LOG_LEVELNoinfoLog verbosity (debug, info, warn, error)

Local Development

bash
# 1. Start Porta infrastructure
yarn docker:up

# 2. Start Porta server
yarn dev

# 3. Run porta init (if not already done)
porta init

# 4. Configure admin GUI
cd admin-gui
cp .env.example .env
# Edit .env with the client ID and secret from porta init

# 5. Start admin GUI in dev mode
yarn dev

The admin GUI will be available at http://localhost:4002.

Docker Deployment

Using Docker Compose

The admin GUI runs as a separate service in the same Docker image, controlled by the PORTA_SERVICE environment variable:

yaml
# In docker/docker-compose.prod.yml
porta-admin:
  image: blendsdk/porta:latest
  environment:
    PORTA_SERVICE: admin
    PORTA_ADMIN_PORT: "4002"
    PORTA_ADMIN_PORTA_URL: http://porta:3000
    PORTA_ADMIN_CLIENT_ID: ${ADMIN_CLIENT_ID}
    PORTA_ADMIN_CLIENT_SECRET: ${ADMIN_CLIENT_SECRET}
    PORTA_ADMIN_SESSION_SECRET: ${ADMIN_SESSION_SECRET}
    REDIS_URL: redis://redis:6379/1
    NODE_ENV: production
  ports:
    - "127.0.0.1:4002:4002"
  depends_on:
    porta:
      condition: service_healthy

Service Modes

The Porta Docker image supports two service modes via PORTA_SERVICE:

ValueDescription
server (default)Runs the Porta OIDC server
adminRuns the Admin GUI BFF

Authentication Flow

  1. User visits the admin GUI at http://localhost:4002
  2. BFF redirects to Porta's OIDC authorization endpoint
  3. User authenticates via magic link (passwordless email)
  4. Porta redirects back to /auth/callback with an authorization code
  5. BFF exchanges the code for tokens using the client secret
  6. BFF stores the session in Redis and sets a session cookie
  7. Subsequent API requests are proxied through the BFF with Bearer token injection

Security

The BFF implements multiple security layers:

  • httpOnly session cookies — tokens never reach the browser
  • CSRF double-submit cookies — protects state-changing requests
  • Server-side Bearer injection — API requests authenticated server-side
  • Security headers — CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
  • Session timeouts — 30-minute idle, 8-hour absolute maximum
  • PKCE (S256) — Proof Key for Code Exchange for the authorization flow

BFF Server Components

The BFF server (admin-gui/src/server/) includes:

ComponentDescription
index.tsServer entry point, startup orchestration, graceful shutdown
config.tsZod-validated environment configuration
oidc.tsOIDC client discovery and token management
session.tsRedis-backed session configuration
routes/auth.tsLogin, callback, logout, session info endpoints
routes/api-proxy.tsAuthenticated API proxy to Porta server
routes/health.tsBFF health check (Redis + Porta connectivity)
routes/spa.tsStatic file serving + SPA fallback
middleware/csrf.tsCSRF double-submit cookie validation
middleware/security-headers.tsSecurity header injection
middleware/session-guard.tsSession authentication guard
middleware/request-logger.tsRequest logging with request ID

Foundational Client Components

The following client-side components are included as a foundation for the SPA rebuild:

ComponentDescription
api/client.tsTyped fetch wrapper with CSRF injection, 401 redirect, ETag support
hooks/useAuth.tsxAuth context — fetches /auth/me, manages login/logout, CSRF
hooks/useTheme.tsFluentUI light/dark theme with localStorage persistence
hooks/useToast.tsFluentUI toast notification wrapper
hooks/useCopyToClipboard.tsClipboard API utility hook
components/ErrorBoundary.tsxReact error boundary with retry UI
components/ToastProvider.tsxFluentUI Toaster wrapper
components/StatusBadge.tsxStatus → colored FluentUI badge mapping
components/CopyButton.tsxClick-to-copy button
components/EmptyState.tsxEmpty state display with icon + action
components/LoadingSkeleton.tsxLoading shimmer placeholders
components/ConfirmDialog.tsxConfirmation dialog
theme.tsFluentUI theme definitions

Testing

BFF Server Tests

The BFF server has comprehensive unit tests:

bash
cd admin-gui
yarn test
Test FileCoverage
tests/server/config.test.tsConfiguration validation
tests/server/csrf.test.tsCSRF protection
tests/server/health.test.tsHealth check endpoint
tests/server/security-headers.test.tsSecurity header injection
tests/server/session-guard.test.tsSession authentication

Placeholder Test

A basic placeholder test validates the SPA renders correctly:

Test FileCoverage
tests/client/placeholder.test.tsxRenders placeholder page, auth controls, theme toggle

Troubleshooting

BFF won't start

  1. Verify Porta server is running and accessible at PORTA_ADMIN_PORTA_URL
  2. Verify Redis is running and accessible at REDIS_URL
  3. Check that PORTA_ADMIN_CLIENT_ID and PORTA_ADMIN_CLIENT_SECRET are correct
  4. Ensure PORTA_ADMIN_SESSION_SECRET is at least 32 characters

Authentication fails

  1. Verify the admin GUI client exists: porta client list
  2. Check redirect URI matches: should be http://localhost:4002/auth/callback
  3. Verify the organization slug is correct (auto-detected from super-admin org)
  4. Check Porta server logs for OIDC errors

Cannot connect to Porta API

  1. Verify PORTA_ADMIN_PORTA_URL is correct and reachable from the BFF
  2. In Docker, use the service name (e.g., http://porta:3000), not localhost
  3. Check that the BFF health endpoint reports both checks as "ok": GET /health

Released under the MIT License.