Skip to main content

Authentication

OAuth2/OIDC authorization, WebAuthn passwordless login, magic link authentication, JWT token lifecycle, and session management built into every layer of systemprompt.io.

TL;DR --- systemprompt.io ships with OAuth2/OIDC for API and MCP authorization, WebAuthn passkeys for passwordless login, magic links for cross-device sign-in, and JWT-based session management. Every authentication event is logged for audit. No external identity provider is required.

What It Does and Why It Matters

systemprompt.io embeds a complete authentication and authorization system directly in your deployment. OAuth2 with OpenID Connect secures every API call and MCP tool execution. WebAuthn eliminates password vulnerabilities for interactive users. Magic links enable device onboarding without passwords. JWT tokens carry scoped claims through every request.

This is not an optional add-on. Authentication is woven into every layer of the library. When Claude Desktop connects to a systemprompt.io MCP server, OAuth2 handles the authorization. When a user logs into your AI product, WebAuthn provides phishing-resistant credentials. When agents communicate via A2A, scoped tokens enforce proper authorization boundaries.

Because systemprompt.io is a library you embed and own, you control the entire auth stack. There is no third-party identity provider to configure, no external dependency to manage, and no vendor lock-in on your users' credentials.

Three-Layer Architecture

The authentication system is organized into three layers, each addressing a distinct security concern.

Identity Layer

Determines who is making a request. This layer handles user registration, credential verification (WebAuthn passkeys, magic links), and cookie-based session establishment. The extract_user_from_cookie function reads the access_token cookie from incoming requests, validates the JWT signature against the configured secret and issuer, and extracts the subject, username, and email claims.

When no valid session exists, the middleware creates a guest UserContext with an empty user_id, allowing unauthenticated requests to proceed to public routes while blocking access to protected ones.

Authorization Layer

Determines what a requester can do. OAuth2 scopes and role-based checks gate every protected operation. The user_context_middleware fetches the user's roles from the database and populates a UserContext that downstream handlers can inspect. Two additional middleware layers enforce access:

  • require_auth_middleware --- rejects any request where user_id is empty with a 401 Unauthorized response.
  • require_admin_middleware --- rejects any request where the user does not hold the admin role with a 403 Forbidden response.

MCP servers declare their OAuth requirements in their manifest, and the library enforces them before tool execution begins.

Audit Layer

Records what actually happened. Every login, logout, and session event is recorded through the activity system. The middleware spawns background tasks to log authentication events with user IDs and timestamps, creating a complete audit trail for compliance and debugging.

OAuth2/OIDC Flows

systemprompt.io implements OAuth2 as the authorization framework for all API access. The authorization server exposes standard endpoints compatible with any OAuth2 client.

Endpoints

Endpoint Purpose
/api/v1/core/oauth/authorize Authorization request
/api/v1/core/oauth/token Token exchange
/api/v1/core/oauth/introspect Token introspection
/.well-known/openid-configuration OIDC discovery

Authorization Code Flow with PKCE

The recommended flow for web applications and native apps. PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks.

  1. Client generates a cryptographic code verifier and derives a challenge
  2. User is redirected to the authorization endpoint with the challenge
  3. User authenticates and grants consent
  4. Authorization server redirects back with an authorization code
  5. Client exchanges the code plus original verifier for an access token
# Agent OAuth2 configuration example
securitySchemes:
  oauth2:
    type: oauth2
    flows:
      authorizationCode:
        authorizationUrl: "/api/v1/core/oauth/authorize"
        tokenUrl: "/api/v1/core/oauth/token"
        scopes:
          anonymous: "Public access without authentication"
          user: "Authenticated user access"
          admin: "Administrative operations"

Client Credentials Flow

For service-to-service authentication where no interactive user is involved. MCP servers use this flow when executing commands on behalf of the system.

# Obtain a service token
curl -X POST /api/v1/core/oauth/token \
  -d "grant_type=client_credentials" \
  -d "client_id=mcp-server" \
  -d "client_secret=${CLIENT_SECRET}" \
  -d "scope=admin"

MCP Server OAuth Configuration

Each MCP server declares its OAuth requirements in its manifest. The library enforces these before any tool execution:

# MCP server manifest
oauth:
  required: true
  scopes: ["admin"]
  audience: "mcp"
  client_id: null

When oauth.required is true, the server calls expect_authenticated() at the start of every request, rejecting unauthenticated calls before tool handlers run.

WebAuthn Passwordless Authentication

WebAuthn eliminates passwords entirely. Users authenticate with biometrics (Face ID, Touch ID, Windows Hello) or hardware security keys (YubiKey). This provides phishing-resistant authentication that cannot be stolen, replayed, or guessed.

Registration Flow

  1. User initiates registration
  2. Server generates a cryptographic challenge
  3. User's authenticator creates a public/private key pair
  4. Public key is stored server-side
  5. Private key never leaves the authenticator device

Authentication Flow

  1. User initiates login
  2. Server sends a challenge with the list of allowed credential IDs
  3. Authenticator signs the challenge with the private key
  4. Server verifies the signature against the stored public key
  5. A session cookie is established on success

WebAuthn credentials are bound to the origin (domain), preventing phishing attacks. Even if a user visits a fake site, the authenticator refuses to respond because the origin does not match.

Site Auth Configuration

The MarketplaceExtension declares which paths are protected and which remain public:

fn site_auth(&self) -> Option<SiteAuthConfig> {
    Some(SiteAuthConfig {
        login_path: "/admin/login",
        protected_prefixes: &["/admin"],
        public_prefixes: &["/admin/login", "/admin/register", "/admin/add-passkey"],
        required_scope: "user",
    })
}

The /admin/login, /admin/register, and /admin/add-passkey pages bypass the auth check so users can complete the authentication flow.

Magic links provide a fallback for cross-device sign-in when the user's passkey is not available on the current device. The flow works as follows:

  1. User submits their email address to POST /api/public/auth/magic-link
  2. The system generates a 32-byte random token, hashes it with SHA-256, and stores the hash in the database
  3. An email is sent with a link containing the raw token, pointing to /admin/add-passkey?token=...
  4. User clicks the link within 15 minutes (the token's expiry window)
  5. The system validates the token via POST /api/public/auth/magic-link/validate, marks it as consumed, and returns the associated email
  6. User creates a new passkey on the current device

Security properties of magic links:

  • Tokens are single-use --- consumed atomically on validation
  • Tokens expire after 15 minutes
  • Rate limited to 3 requests per email per 15-minute window
  • Only existing users receive emails (non-existent emails get an identical success response to prevent enumeration)
  • Raw tokens are never stored; only SHA-256 hashes persist in the database
  • IP addresses are recorded for audit

Scope Management

Scopes are the unit of permission in systemprompt.io. Every protected operation requires specific scopes. Tokens carry only the scopes their holder needs.

Built-In Scopes

Scope Permission
anonymous Public access, no authentication required
user Basic authenticated user operations
admin Administrative operations (implies user)
tools:read Read MCP tool definitions
tools:execute Execute MCP tools
agents:read View agent configurations
agents:write Modify agent configurations

Scope Enforcement

Scopes are checked at multiple levels:

  • API routes declare required scopes in their handler configuration
  • MCP tools specify scope requirements in their server manifest (oauth_scopes field)
  • Agents define which scopes are needed to interact with them in their YAML configuration
  • Site auth requires a minimum scope (typically user) for all protected prefixes

When a request arrives, the authorization layer extracts scopes from the access token's claims and compares them against the operation's requirements. If the token lacks the required scopes, the request is rejected with 403 Forbidden.

Scope Hierarchies

The admin scope implies all lesser scopes. This is enforced in the authorization server and simplifies token management for privileged users. Role-based checks in middleware (is_admin) provide an additional authorization layer on top of scope validation.

Token Lifecycle

JWT Structure

Access tokens are JWTs signed with a shared secret. Each token contains:

Claim Description
sub Subject identifier (user ID or client ID)
username Human-readable username
email User email address
scope Space-separated list of granted scopes
aud Intended audience (api, mcp, web, a2a, or a resource like plugin)
exp Expiration timestamp
jti Unique token identifier for revocation tracking
iss Issuer identity (matches jwt_issuer in config)

Token Validation

Every request that carries an access_token cookie goes through validate_jwt_token, which checks:

  1. Signature validity against the configured JWT secret
  2. Issuer matches the jwt_issuer configuration value
  3. Audience matches the expected audience for the endpoint (e.g., JwtAudience::Api)
  4. Token has not expired

If any check fails, the request is treated as unauthenticated.

Token Lifetimes

Token expiration is configured per profile:

# .systemprompt/profiles/local/profile.yaml
security:
  jwt_issuer: "systemprompt-local"
  jwt_access_token_expiration: 2592000     # 30 days
  jwt_refresh_token_expiration: 15552000   # 180 days
  jwt_audiences:
    - web
    - api
    - a2a
    - mcp
Duration Seconds Typical Use Case
1 hour 3,600 High-security environments
24 hours 86,400 Session-based web access
30 days 2,592,000 Development and local profiles
1 year 8,760 hours Long-lived plugin tokens

Plugin tokens generated by generate_plugin_token use a 1-year expiry with JwtAudience::Resource("plugin") to support long-running integrations.

Refresh Tokens

Refresh tokens are stored in an HttpOnly cookie scoped to /api/public/auth with a 30-day lifetime. They are never accessible to client-side JavaScript, reducing the risk of token theft via XSS.

Session Handling

systemprompt.io uses cookie-based sessions rather than server-side session stores. The access_token cookie carries the JWT, and session state is derived from its claims on every request.

Setting a session:

The POST /api/public/auth/session endpoint receives an access token and optional refresh token, then sets two cookies:

  • access_token --- Path=/; SameSite=Lax with the token's expiry as Max-Age. Marked Secure when the request arrives over HTTPS.
  • refresh_token --- Path=/api/public/auth; HttpOnly; SameSite=Lax with a 30-day Max-Age. The HttpOnly flag prevents JavaScript access.

Clearing a session:

The DELETE /api/public/auth/session endpoint sets both cookies to empty values with Max-Age=0, immediately expiring them. Before clearing, it records a logout activity event for audit purposes.

User Context Propagation

The user_context_middleware runs on every request to protected routes. It:

  1. Extracts and validates the JWT from the access_token cookie
  2. Fetches the user's roles from the database
  3. Populates a UserContext struct with user_id, username, email, roles, and is_admin
  4. Injects the context as an Axum extension for downstream handlers
  5. Spawns a background task to record the login activity event

Downstream handlers access the user context via Axum's Extension<UserContext> extractor. The /api/public/auth/me endpoint returns the full user context as JSON for client-side consumption.

Security Best Practices

These security properties are enforced by default:

  • TLS required --- all production traffic must use HTTPS. The Secure cookie flag is set automatically when HTTPS is detected via X-Forwarded-Proto or the Origin header.
  • No password storage --- WebAuthn and magic links eliminate passwords entirely. There is no password hash to leak.
  • Token rotation --- access tokens have configurable short lifetimes. Refresh tokens are scoped to a narrow cookie path and marked HttpOnly.
  • Credential isolation --- JWT secrets are loaded from SecretsBootstrap at validation time, never logged, and never exposed in error messages.
  • Tenant boundaries --- tokens can carry tenant_id claims for multi-tenant isolation, ensuring one tenant's token cannot access another's resources.
  • Rate limiting --- magic link requests are capped at 3 per email per 15-minute window. Responses are identical for existing and non-existing emails to prevent enumeration.
  • Single-use tokens --- magic link tokens are atomically consumed on validation. Replaying a used token returns an error.
  • Audit logging --- every login, logout, and registration event is recorded with user IDs and timestamps through the activity system.
  • Origin binding --- WebAuthn credentials are cryptographically bound to the origin, making phishing attacks ineffective.

CLI Reference

Authentication is managed through the cloud auth and admin session command groups.

Command Description
systemprompt cloud auth login Authenticate via OAuth (opens browser for WebAuthn)
systemprompt cloud auth logout Clear saved cloud credentials
systemprompt cloud auth whoami Show current authenticated user and token status
systemprompt admin session show Show current session and routing info
systemprompt admin session switch Switch to a different profile
systemprompt admin session list List available profiles
systemprompt admin session login Create an admin session for CLI access
systemprompt admin session logout Remove a session
systemprompt admin session refresh Refresh the current access token

Run systemprompt cloud auth --help and systemprompt admin session --help for detailed options.