Skip to content

Test Provider ​

During development you often iterate on features unrelated to auth. Standing up a database, registering a user, and logging in on every cycle slows you down. TestProvider removes that friction: it implements AuthProvider with a single fixed identity, authenticates requests with or without a session token, and needs no database or network.

Production warning

TestProvider authenticates every request unconditionally. It must never be used in production. It is gated behind the test-provider Cargo feature to prevent accidental inclusion.

Setup ​

Add the surge crate with the test-provider feature:

toml
[dependencies]
surge = { version = "0.1", features = ["test-provider"] }

Creating the provider ​

rust
use std::sync::Arc;
use surge::{AuthProvider, TestConfig};

let provider: Arc<dyn AuthProvider> = surge::test(TestConfig::default())?;

That's it — no database URL, no pepper, no migrations. The provider is ready immediately.

A warning is logged at construction so you'll always know when it's active:

WARN surge::test_provider: TestProvider active -- every request is authenticated as this identity. DO NOT use in production. username=test-user

Custom identity ​

Override the defaults by passing your own TestConfig:

rust
let provider = surge::test(TestConfig {
    username: "dev-alice".into(),
    display_name: "Alice (dev)".into(),
})?;

The username must pass Surge's standard validation (3–32 lowercase alphanumeric characters and single hyphens).

Behavior ​

MethodBehavior
verify_sessionAccepts Some(any aeg_s_* token) or None. Always returns the fixed identity.
authenticate_passwordAlways succeeds, ignoring username and password.
registerReturns the fixed identity without creating anything.
register_and_authenticateReturns the fixed identity with a session token.
identity / identity_by_usernameReturns the fixed identity regardless of arguments.
update_profileApplies the patch in memory; subsequent calls reflect the change.
revoke_session / revoke_all_sessionsNo-ops — the provider stays "always authenticated".
browser_routerServes GET /v1/whoami only. Every other perimeter route returns 501.

Browser router ​

TestProvider mounts a read-only perimeter, so a frontend can answer "am I logged in, and who am I?" without a database:

rust
let app = Router::new()
    .merge(Arc::clone(&provider).browser_router(BrowserRouterConfig {
        cookie_domain: "localhost".into(),
        session_ttl: Duration::from_secs(3600),
        auth_ui_origin: "http://localhost:5173".into(),
        session_cors_origins: vec![],
        // Embedded-only fields are ignored — no rate limiter needed.
        rate_limiter: None,
        return_origins: None,
        registration: None,
        factor_policy: None,
        allow_inline: None,
        oauth_bridge: None,
        maintenance_interval: None,
    }));

GET /v1/whoami returns the same session JSON as the real perimeter (Whoami), always 200 — there is no unauthenticated state to represent. It carries no policy block, since the fixed identity enrolls no factors.

Everything else — /v1/login, the flow routes, /v1/logout, the factor routes — answers 501 not_implemented. The identity is fixed, so there is no credential to submit and no session to revoke; returning 501 surfaces that instead of faking success. If your frontend calls logout(), expect it to fail under TestProvider and branch accordingly.

Using with the AuthSession extractor ​

TestProvider works with the standard AuthSession extractor. The extractor reads and parses the token from a surge_session cookie or Authorization: Bearer header, then calls verify_session. A missing or malformed carrier is passed as None; the test provider always succeeds, so no cookie or header is required.

Your service code needs no changes:

rust
use surge::AuthSession;

async fn dashboard(AuthSession(session): AuthSession) -> String {
    format!("Hello, {}", session.identity.display_name)
}

Requests may omit authentication entirely. They can also provide any validly-prefixed token:

bash
curl http://localhost:3000/dashboard \
  -H "Authorization: Bearer aeg_s_anything"

Switching providers by environment ​

A common pattern is to select the provider at startup based on an environment variable:

rust
let provider: Arc<dyn AuthProvider> = if cfg!(feature = "test-provider")
    && std::env::var("SURGE_TEST_PROVIDER").as_deref() == Ok("true")
{
    surge::test(TestConfig::default())?
} else {
    surge::embedded(EmbeddedConfig { /* ... */ }).await?
};

This keeps production builds clean — when test-provider isn't in your feature set, the branch compiles away entirely.