Bastion

Authentication

Request authentication via API keys, Bearer tokens, and forward auth.

Bastion provides gateway-level authentication that validates incoming requests before they reach upstream services. It supports multiple authentication strategies, per-route overrides, and integration with the Forge auth extension.

Configuration

bastion.WithAuth(bastion.AuthConfig{
    Enabled:        true,
    DefaultPolicy:  "deny",
    Providers:      []string{"api-keys", "jwt-tokens"},
    ForwardHeaders: true,
})

Configuration Fields

FieldTypeDefaultDescription
EnabledboolfalseEnable or disable gateway authentication
DefaultPolicystring""Default policy when no providers are configured ("allow" or "deny")
Providers[]string[]Ordered list of provider names to try (OR logic)
ForwardHeadersbooltrueForward authenticated identity to upstream via headers

Authentication Flow

When a request arrives and auth is enabled:

  1. Check if the route has a per-route auth override. If SkipAuth is true, the request passes through.
  2. Determine which providers to use (per-route providers override global providers).
  3. Try each provider in order. If any provider returns a valid AuthContext, the request is authenticated.
  4. If all providers fail, return 401 Unauthorized.
  5. If the route requires specific scopes, verify the AuthContext has all required scopes. Return 403 Forbidden if not.
  6. If ForwardHeaders is enabled, inject identity headers for the upstream.

Built-in Providers

API Key Authentication

Validates requests using API keys sent in a header or query parameter:

apiKeyProvider := bastion.NewAPIKeyAuthProvider(
    "api-keys",       // Provider name
    "X-API-Key",      // Header to check (default: "X-API-Key")
    "api_key",        // Query param to check (optional)
    []*bastion.APIKeyEntry{
        {
            Key:     "sk_live_abc123",
            Subject: "service-a",
            Scopes:  []string{"read", "write"},
        },
        {
            Key:     "sk_live_def456",
            Subject: "service-b",
            Scopes:  []string{"read"},
        },
    },
)

The provider checks the header first, then falls back to the query parameter. Each key maps to a subject (identity) and optional scopes.

Bearer Token Authentication

Validates Bearer tokens in the Authorization header using a custom validation function:

bearerProvider := bastion.NewBearerTokenAuthProvider(
    "jwt-tokens",
    func(ctx context.Context, token string) (*bastion.GatewayAuthContext, error) {
        // Validate the token (e.g., verify JWT signature, check expiry)
        claims, err := validateJWT(token)
        if err != nil {
            return nil, err
        }
        return &bastion.GatewayAuthContext{
            Subject: claims.Subject,
            Scopes:  claims.Scopes,
            Claims:  map[string]any{"role": claims.Role},
        }, nil
    },
)

The provider extracts the token from the Authorization: Bearer <token> header and passes it to the validation function.

Forward Auth

Delegates authentication to an external auth service. The gateway forwards selected headers to the auth endpoint and interprets the response:

forwardAuthProvider := bastion.NewForwardAuthProvider(
    "external-auth",
    "https://auth.example.com/verify",
    []string{"Authorization", "Cookie", "X-Forwarded-For"},
)

Forward auth behavior:

  1. The gateway sends a GET request to the auth endpoint.
  2. Configured headers from the original request are forwarded.
  3. X-Original-URI and X-Original-Method headers are added for context.
  4. A 200 response means authenticated. The auth service returns identity info in response headers.
  5. Any non-200 response means unauthenticated. The status code is forwarded to the client.

Response headers from the auth service that populate the AuthContext:

HeaderMaps To
X-Auth-SubjectAuthContext.Subject
X-Auth-RoleAuthContext.Claims["role"]
X-Auth-ScopesAuthContext.Scopes (comma-separated)

Registering Providers

Register auth providers on the gateway:

gateway.RegisterAuthProvider(apiKeyProvider)
gateway.RegisterAuthProvider(bearerProvider)
gateway.RegisterAuthProvider(forwardAuthProvider)

Per-Route Auth

Individual routes can override authentication settings:

bastion.WithRoute(bastion.RouteConfig{
    Path: "/api/admin/*",
    Auth: &bastion.RouteAuthConfig{
        Enabled:   true,
        Providers: []string{"jwt-tokens"},
        Scopes:    []string{"admin"},
    },
    Targets: []bastion.TargetConfig{
        {URL: "http://admin-svc:8080"},
    },
})

RouteAuthConfig Fields

FieldTypeDescription
EnabledboolEnable auth for this route
Providers[]stringProviders to use (overrides global list)
Scopes[]stringRequired scopes (all must be present)
SkipAuthboolSkip authentication entirely for this route
ForwardAuthboolForward auth headers to upstream

Skipping Auth for Public Routes

bastion.WithRoute(bastion.RouteConfig{
    Path: "/public/*",
    Auth: &bastion.RouteAuthConfig{
        SkipAuth: true,
    },
    Targets: []bastion.TargetConfig{
        {URL: "http://public-svc:8080"},
    },
})

Auth Context

A successful authentication returns an AuthContext:

type AuthContext struct {
    Subject      string         // Authenticated entity (user ID, service ID)
    Claims       map[string]any // Additional claims (role, email, etc.)
    Scopes       []string       // OAuth2 scopes or permissions
    ProviderName string         // Which provider authenticated this request
}

The AuthContext can be checked for scopes:

authCtx.HasScope("admin")           // Check a single scope
authCtx.HasScopes("read", "write")  // Check all scopes (AND logic)

Forwarded Headers

When ForwardHeaders is true and authentication succeeds, the gateway injects the following headers into the upstream request:

HeaderValue
X-Auth-SubjectThe authenticated subject
X-Auth-ProviderThe provider that authenticated the request
X-Auth-ScopesComma-separated scopes
X-Auth-RoleThe role claim (if present)
X-Auth-EmailThe email claim (if present)

This allows upstream services to trust the gateway's authentication and extract identity information without re-validating credentials.

Integration with Forge Auth Extension

Bastion can integrate with the Forge auth extension's provider registry. When the Forge auth extension is loaded alongside Bastion, any providers registered in the Forge auth registry become available to the gateway:

// Forge auth providers are automatically available
// e.g., if Forge auth has a "jwt" provider registered,
// the gateway can reference it by name
bastion.WithAuth(bastion.AuthConfig{
    Enabled:   true,
    Providers: []string{"jwt"}, // References Forge auth provider
})

The gateway checks its own local providers first, then falls back to the Forge auth registry.

On this page