Authentication
OAuth2/OIDC authorization, WebAuthn passwordless login, magic link authentication, JWT token lifecycle, and session management built into every layer of systemprompt.io.
On this page
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 whereuser_idis empty with a401 Unauthorizedresponse.require_admin_middleware--- rejects any request where the user does not hold theadminrole with a403 Forbiddenresponse.
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.
- Client generates a cryptographic code verifier and derives a challenge
- User is redirected to the authorization endpoint with the challenge
- User authenticates and grants consent
- Authorization server redirects back with an authorization code
- 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
- User initiates registration
- Server generates a cryptographic challenge
- User's authenticator creates a public/private key pair
- Public key is stored server-side
- Private key never leaves the authenticator device
Authentication Flow
- User initiates login
- Server sends a challenge with the list of allowed credential IDs
- Authenticator signs the challenge with the private key
- Server verifies the signature against the stored public key
- 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 Link Authentication
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:
- User submits their email address to
POST /api/public/auth/magic-link - The system generates a 32-byte random token, hashes it with SHA-256, and stores the hash in the database
- An email is sent with a link containing the raw token, pointing to
/admin/add-passkey?token=... - User clicks the link within 15 minutes (the token's expiry window)
- The system validates the token via
POST /api/public/auth/magic-link/validate, marks it as consumed, and returns the associated email - 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_scopesfield) - 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:
- Signature validity against the configured JWT secret
- Issuer matches the
jwt_issuerconfiguration value - Audience matches the expected audience for the endpoint (e.g.,
JwtAudience::Api) - 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
Cookie-Based Sessions
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=Laxwith the token's expiry asMax-Age. MarkedSecurewhen the request arrives over HTTPS.refresh_token---Path=/api/public/auth; HttpOnly; SameSite=Laxwith a 30-dayMax-Age. TheHttpOnlyflag 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:
- Extracts and validates the JWT from the
access_tokencookie - Fetches the user's roles from the database
- Populates a
UserContextstruct withuser_id,username,email,roles, andis_admin - Injects the context as an Axum extension for downstream handlers
- 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
Securecookie flag is set automatically when HTTPS is detected viaX-Forwarded-Protoor theOriginheader. - 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
SecretsBootstrapat validation time, never logged, and never exposed in error messages. - Tenant boundaries --- tokens can carry
tenant_idclaims 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.
Related Documentation
- Users Service --- user registration, roles, and profile management
- Security Configuration --- JWT issuer, token expiration, and audience settings
- Credentials Configuration --- managing secrets and encryption keys
- Sessions Configuration --- session store and cookie settings