Skip to content

Troubleshooting

wiki edited this page Sep 4, 2026 · 1 revision

Troubleshooting

The application refuses to start with ErrInsecureConfiguration

That is the feature. The message names every problem:

- GET /admin/users requires scheme "jwt", which is not registered
- DELETE /users/{id} declares roles for scheme "apikey", which cannot enforce them

"not registered" — a typo in RequiredSchemes, or the scheme was never passed to WithScheme.

"cannot enforce them" — the scheme's validator does not implement RoleEnforcer / ScopeEnforcer, or its SupportsRoles() returns false. Implement it on the validator; see Roles and Scopes.

Do not work around this by removing the role declaration. The declaration is correct — the enforcement is missing.

My secured route is public

Almost always a pointer receiver on a value registration:

func (r *AdminRoute) RequiredSchemes() []string { … } // pointer receiver

app.RegisterRoute(AdminRoute{Route: rt})  // ← value: assertion fails, route is public
app.RegisterRoute(&AdminRoute{Route: rt}) // ← right

Nothing reports it, because "does not implement SecuredRoute" is indistinguishable from "deliberately public". A custom RouteValidator with an explicit allowlist of public paths is the only mechanism that catches this class of mistake.

Roles are declared but never enforced

Check SupportsRoles() on the validator. If it returns false — or the validator does not implement RoleEnforcer at all — the framework now refuses to start rather than serving the route to everyone. If it is starting, the route is not declaring roles for the scheme you think it is: the map key must match a name in RequiredSchemes.

Everything returns 401 and I cannot tell why

The client is deliberately told nothing. Look at the log — the specifics go to MiddlewareConfig.Logger, which the extension sets from r.Logger().

If there is no log line either, something replaced the middleware config with one whose Logger is nil.

At LogLevelDebug the middleware records which scheme rejected and why.

NewBearerScheme panicked at startup

A nil validator. A scheme with no validator cannot authenticate anything, so every request to a route requiring it would fail at request time — with a nil dereference or a permanent 401 depending on the path. Panicking at construction puts the failure at the line that caused it.

CSRF rejects my own application's requests

A non-empty allowlist does not implicitly allow the application's own origin. Browsers send Origin on unsafe same-origin requests too, so add it:

security.WithCSRFPolicy(rextension.OriginPolicy{
	AllowedOrigins: []string{"https://app.example.com"}, // ← including your own
})

This is invisible in a BFF layout where the SPA is on another port, and bites the moment the two are served together.

CSRF rejects a request that carries the token

  • The cookie is not being sent. credentials: "include" on the fetch, and the cookie must not be Secure if you are on plain HTTP — WithCSRFInsecureTransport() for local development.
  • The header name does not match. Default X-CSRF-Token.
  • A form body with no header. The form field is checked instead; default csrf_token.
  • __Host- prefix without its requirements. That prefix needs Secure, no Domain, and Path=/. On http://localhost the browser silently refuses to set the cookie.

CSRF is protecting a route it should not

It attaches only to unsafe methods on routes authenticated by a cookie-based scheme. If your route is one and should be exempt — a webhook receiver authenticated by a signature, say — declare it:

func (r *WebhookRoute) CSRFExempt() bool { return true }

Do not reach for WithoutCSRF() unless the whole application serves no browser-facing cookie session.

Sessions expire immediately

SessionStore.Set is probably ignoring expiresAt, or Touch is not implemented and returns an error. Both compile silently.

If sessions never expire instead, Set is ignoring expiresAt in the other direction — the store is authoritative for expiry, so a store that does not honour the deadline has no expiry at all.

Sessions expire while the user is active

Touch is not being called, or it is not extending. ValidateSession refreshes the idle deadline on every successful validation — that is what makes an idle timeout an idle timeout.

Note it never pushes past the absolute deadline, so a user working continuously past 12 hours is logged out by design.

A user re-authenticates and the absolute deadline does not reset

By design. RotateSession carries the absolute deadline over rather than resetting it — resetting would let an attacker with a valid session extend its lifetime indefinitely by re-authenticating, which is the bound rotation exists to preserve.

Two principals and I only get one

Use GetPrincipalForScheme:

svc, _ := security.GetPrincipalForScheme(r, "serviceToken")
usr, _ := security.GetPrincipalForScheme(r, "session")

GetPrincipal returns the primary principal — the first scheme to authenticate — which is right for a single-scheme route and ambiguous for anything else.

The OpenAPI document shows no security schemes

The generator resolves rextension.SchemeRegistry from the container. If the security extension is not registered, or is registered but has no schemes, there is nothing to document.

Check the boot order is not the issue — the registry goes into the container during security's OnInitialize, and OpenAPI reads it in OnStart.

Session cookie is missing Secure in production

CookieOptions.AllowInsecureTransport is set. It should be false everywhere except http://localhost. There is no Secure field — the zero value produces Secure, so the only way to lose it is to have asked.

Two Rex instances in one process interfere

They should not any more: the scheme registry is an instance in each application's container, not a package-level global. If you see cross-talk, something is still calling the deprecated rextension.RegisterSecuritySchemes.

Clone this wiki locally