DEV Community

johnettagaweraks
johnettagaweraks

Posted on

SAML RelayState is state, not an authorization claim

SAML RelayState is state, not an authorization claim

SAML bindings may include a RelayState value alongside the protocol message. Service providers commonly use it to resume a user's original request after an identity provider returns an assertion. The value is transport state. It should not become evidence that the user was authorized, and it should not be accepted as an arbitrary redirect target simply because it arrived from an identity provider.

A safe design stores the service-provider transaction on the server and gives the browser an opaque, random handle. The handle maps to the original request, the target tenant, the relay destination and a short expiry. When the response returns, validate the handle, consume it once, and then continue with the saved context. If the value is signed but contains all of the state, the service provider still needs a rule for which fields are allowed to influence the session and where the browser may be redirected.

Redirect validation is a separate control. An allowlist of relative paths or known application origins prevents an attacker from turning RelayState into an open redirect. Reject scheme-relative destinations, backslash variants and values that require host parsing rules the application does not fully understand. If the product supports external partner destinations, store the destination as a server-side identifier rather than a full URL supplied by the requester.

Bind the state to more than the browser session where possible. Include the request's intended application, a hash of the initiating context, and a nonce associated with the SAML request. The response may arrive after a timeout, through a different worker, or after the user has opened several tabs. A one-time handle with an explicit lifecycle prevents an old response from resuming a new transaction.

IdP-initiated flows need an explicit rule. There may be no locally created RelayState because the identity provider starts the exchange. The service provider can still choose a safe landing page and should not infer the target from an unsigned query parameter. Validate the assertion, identify the subject and tenant through trusted attributes, and then route to a destination that the application policy allows.

Error handling should not leak state. If the handle is expired, unknown, already used or bound to a different session, return a generic failure and record the reason internally. Do not echo the state into logs if it can contain sensitive tenant information. Keep enough context to investigate repeated failures, but treat RelayState as a bearer-like value that could be copied through browser history, referrers or support tickets.

The most important test is negative. Start a login for tenant A, capture the RelayState, complete login for tenant B, and try to reuse A's handle. Repeat after changing the target path or starting a fresh session. The expected result is rejection or a safe default. A successful login with the wrong tenant is a session-binding defect, not a cosmetic routing bug.

RelayState is useful because it connects two HTTP exchanges that otherwise have no memory of each other. Keep that memory on the server, make the handle unguessable and one-time, constrain the destination, and never let the parameter decide identity or privilege.

References

Top comments (0)