-
Notifications
You must be signed in to change notification settings - Fork 0
Configuration
app := rex.New(security.WithSecurity(security.NewConfig(
security.WithScheme(bearerScheme),
security.WithScheme(sessionScheme),
security.WithCSRFPolicy(origins),
)))security.WithSecurity(nil) takes the defaults — which register no schemes,
so every route declaring one fails startup validation.
There is no field-by-field merge. A partial struct literal leaves everything else at its zero value:
// Wrong: Schemes nil, CSRF nil.
security.WithSecurity(&security.Config{DisableCSRF: true})
// Right.
security.WithSecurity(security.NewConfig(
security.WithScheme(s),
security.WithoutCSRF(),
))The merge used to exist and behaved as a trap: fields it forgot were silently ignored, fields it copied unconditionally were zeroed by a partial literal, and a deliberate zero could not be expressed at all.
type Config struct {
Schemes []SecurityScheme
DisableCSRF bool // phrased as a negative on purpose
CSRF *CSRFConfig // nil takes the defaults
}DisableCSRF is a negative so that turning protection off is something you
have to write. See CSRF → Why default-on.
WithScheme(s) |
register a scheme |
WithCSRFPolicy(p) |
the origin allowlist CSRF checks against |
WithCSRF(cfg) |
replace the whole CSRF configuration |
WithCSRFInsecureTransport() |
token cookie over plain HTTP — local dev only |
WithoutCSRF() |
disable protection application-wide |
An application trusts one set of origins. Writing the list twice is how the two drift apart.
origins := rextension.OriginPolicy{
AllowedOrigins: []string{"https://app.example.com"},
AllowCredentials: true,
}
app := rex.New(
cors.WithCORS(cors.NewConfig(cors.WithPolicy(origins))),
security.WithSecurity(security.NewConfig(
security.WithScheme(scheme),
security.WithCSRFPolicy(origins),
)),
)OriginPolicy is declared in rextension, so neither module imports the other.
func securityConfig(origins rextension.OriginPolicy, store security.SessionStore) *security.Config {
sessions := security.NewSessionStoreValidator(store,
security.WithIdleTimeout(30*time.Minute),
security.WithAbsoluteTimeout(12*time.Hour),
)
return security.NewConfig(
// Machine-to-machine.
security.WithScheme(
security.NewBearerScheme("serviceToken", tokenValidator).
SetBearerFormat("JWT").
SetRolesClaim("realm_access.roles"),
),
// Browser session.
security.WithScheme(
security.NewSessionCookieScheme("session", "session_id", sessions).
WithCookieOptions(security.CookieOptions{
MaxAge: int((12 * time.Hour).Seconds()),
SameSite: http.SameSiteLaxMode,
HttpOnly: true,
// AllowInsecureTransport false → Secure
}),
),
security.WithCSRFPolicy(origins),
)
}Local development adds exactly two things, and nothing else changes:
security.WithCSRFInsecureTransport()
// and CookieOptions{AllowInsecureTransport: true}var reg *security.SchemeRegistry
_ = app.Container().Resolve(®)
reg.All() // []SecurityScheme, in registration order
reg.Get("jwt") // one scheme, or nil
reg.Names() // registration orderIt also satisfies rextension.SchemeRegistry, which is how the OpenAPI
extension reads it without importing this module.
type MiddlewareConfig struct {
SchemeRegistry *SchemeRegistry
Logger rx.Logger
}Set the logger. The problem document a client receives is deliberately generic — a validator's error text routinely names another account, an internal host, or the shape of the authorization model. Without somewhere to write the specifics they are simply lost, which makes the redaction rule unmaintainable in practice.
The extension configures this for you from r.Logger().
rextension-security — authentication, authorization and CSRF for Rex · MIT · © 2026 Kryovyx