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
| Field | Type | Default | Description |
|---|---|---|---|
Enabled | bool | false | Enable or disable gateway authentication |
DefaultPolicy | string | "" | Default policy when no providers are configured ("allow" or "deny") |
Providers | []string | [] | Ordered list of provider names to try (OR logic) |
ForwardHeaders | bool | true | Forward authenticated identity to upstream via headers |
Authentication Flow
When a request arrives and auth is enabled:
- Check if the route has a per-route auth override. If
SkipAuthis true, the request passes through. - Determine which providers to use (per-route providers override global providers).
- Try each provider in order. If any provider returns a valid
AuthContext, the request is authenticated. - If all providers fail, return 401 Unauthorized.
- If the route requires specific scopes, verify the
AuthContexthas all required scopes. Return 403 Forbidden if not. - If
ForwardHeadersis 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:
- The gateway sends a
GETrequest to the auth endpoint. - Configured headers from the original request are forwarded.
X-Original-URIandX-Original-Methodheaders are added for context.- A
200response means authenticated. The auth service returns identity info in response headers. - Any non-200 response means unauthenticated. The status code is forwarded to the client.
Response headers from the auth service that populate the AuthContext:
| Header | Maps To |
|---|---|
X-Auth-Subject | AuthContext.Subject |
X-Auth-Role | AuthContext.Claims["role"] |
X-Auth-Scopes | AuthContext.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
| Field | Type | Description |
|---|---|---|
Enabled | bool | Enable auth for this route |
Providers | []string | Providers to use (overrides global list) |
Scopes | []string | Required scopes (all must be present) |
SkipAuth | bool | Skip authentication entirely for this route |
ForwardAuth | bool | Forward 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:
| Header | Value |
|---|---|
X-Auth-Subject | The authenticated subject |
X-Auth-Provider | The provider that authenticated the request |
X-Auth-Scopes | Comma-separated scopes |
X-Auth-Role | The role claim (if present) |
X-Auth-Email | The 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.