Skip to content

Running as a Server ​

Run surge-server as a standalone authentication service. Your applications connect over HTTP using Bearer tokens via the RemoteProvider client.

Building and starting the server ​

Build the server binary from source:

bash
cargo build -p surge-server

Or run directly:

bash
cargo run -p surge-server -- serve

The binary is also available as a pre-built Docker image — see Docker.

Required environment variables ​

At minimum, two variables must be set:

bash
export DATABASE_URL="postgres://localhost/surge"
export SURGE_PEPPER="dev-pepper-change-me"  # change this in production

All other settings fall back to defaults. For the full list, see Configuration.

The serve subcommand ​

The serve subcommand starts the HTTP server. It accepts an optional --bind flag that overrides SURGE_BIND:

bash
# Default: reads SURGE_BIND or falls back to 0.0.0.0:3000
surge-server serve

# Explicit bind address (takes precedence over env)
surge-server serve --bind 127.0.0.1:8080

On startup, the server:

  1. Parses environment configuration (ServerConfig::from_env())
  2. Creates an embedded provider (runs database migrations)
  3. Assembles the browser and service-facing API routers
  4. Runs startup coherence checks
  5. Begins listening and accepting traffic

Startup coherence checks ​

Before accepting traffic, the server validates that the configuration is internally consistent:

rust
fn check_startup_coherence(config: &ServerConfig, return_origins: &[String]) -> anyhow::Result<()> {
    // Warn if no return_origins are registered — every GET /login redirect will fail
    if return_origins.is_empty() { /* warn */ }

    // Bail if CORS origins are set but don't include the auth UI origin
    if !config.session_cors_origins.is_empty()
        && !config.session_cors_origins.iter().any(|o| o == &config.auth_ui_origin)
    { /* bail */ }

    // Warn if served+inline is enabled — the operator should understand the tradeoff
    if config.allow_served_inline { /* warn */ }

    Ok(())
}

These checks catch misconfigurations at boot rather than letting them surface as cryptic runtime errors during a real login attempt.

Health check ​

The server exposes a GET /health endpoint that returns 204 No Content when the database is reachable:

bash
curl -i http://localhost:3000/health
HTTP/1.1 204 No Content

Use this for container health checks, load balancer probes, and monitoring. See Health Checks for details on Kubernetes probes and container orchestration.

Connecting client applications ​

RemoteProvider (Rust) ​

Applications connect to surge-server over HTTP using RemoteProvider, available from the surge crate with the remote feature:

toml
[dependencies]
surge = { version = "0.1", features = ["remote"] }
rust
use std::sync::Arc;
use std::time::Duration;
use surge::{RemoteConfig, RemoteProvider, AuthProvider};

let provider: Arc<dyn AuthProvider> = Arc::new(
    RemoteProvider::new(RemoteConfig {
        base_url: "https://auth.example.com".parse()?,
        service_token: SecretString::from("aeg_svc_...".to_string()),
        cache_ttl: Duration::from_secs(60),
        cache_max_entries: 10_000,
        timeout: Duration::from_secs(5),
    })?
);

RemoteConfig fields:

FieldPurpose
base_urlURL of the surge-server instance
service_tokenBearer token with appropriate grants
cache_ttlHow long session verifications are cached (moka in-memory cache)
cache_max_entriesMaximum number of cached session entries
timeoutHTTP request timeout

Moka caching ​

RemoteProvider uses the moka crate for in-memory caching of session verifications. When your service calls verify_session(), the result is cached for cache_ttl. Subsequent calls for the same session within that window skip the network round trip entirely.

The cache key is a byte hash of the session token — tokens are never stored in plaintext in the cache. Cache eviction follows a TTL-based policy, not LRU.

Browser-facing apps: cross-origin with CORS ​

When your frontend runs on a different origin than surge-server, you need to configure the cross-origin session-management zone:

bash
# Allow your apps to call /v1/whoami and /v1/logout from their own origins
export SURGE_SESSION_CORS_ORIGINS="https://auth.panit.dev,https://app.example.com"

The credential-entry zone (/v1/login, /v1/flows/*) is always restricted to the auth UI origin — cross-origin credential submission is never allowed.

Browser-facing apps: same-origin via proxy ​

If you want to avoid CORS entirely, proxy requests to surge-server from your application server. Your frontend calls /api/auth/v1/whoami on your domain, your server forwards to https://auth.internal:3000/v1/whoami. Everything is same-origin — no CORS configuration needed.

Service token management ​

Manage service tokens via the CLI:

bash
# Create a service with grants
surge-server svc create \
  --name my-app \
  --grant introspect \
  --grant identity_read \
  --origin https://app.example.com

# Output:
# Service created:
#   ID:     018f9a1b-...
#   Name:   my-app
#   Grants: ["introspect", "identity_read"]
#   Token:  aeg_svc_1a2b3c4d5e6f7g8h9i0j
#
# Store this token securely — it cannot be retrieved again.

# List all registered services
surge-server svc list

# Revoke a service token
surge-server svc revoke my-app

Available grants:

GrantAllows
introspectVerify sessions and get session metadata
identity_readLook up identities by ID or username
identity_writeCreate and update identities
direct_authAuthenticate users by password
revokeRevoke sessions

The --origin flag registers return origins for redirect-mode login flows. Each service that participates in browser login redirects should register its origin.

Identity management via CLI ​

The CLI manages existing identities but does not create them — in closed registration mode, identity creation happens only through the service API (identity_write grant; see Registration Modes).

bash
# Generate and print a random temporary password, revoking all sessions
surge-server identity reset-password alice

# Enable/disable an identity (also revokes sessions on disable)
surge-server identity enable alice
surge-server identity disable alice

# Rename an identity (does not update downstream systems automatically)
surge-server identity rename alice alice2