Legacy backends were never designed to accept modern identity
Many applications running inside an enterprise — internal portals, billing systems, ops consoles, vendor-supplied admin panels, legacy line-of-business tools — were built before SAML or OIDC became mainstream. They typically recognise the user not via modern tokens, but through an HTTP header, a Basic-Auth credential, or a session cookie they already trust.
Migrating these apps to modern SSO sounds straightforward in theory: rewrite the backend authentication path, add SAML/OIDC support, and wire the app to the central identity provider. In practice it almost never happens. The source may be old, the app may be vendor-owned, the change may break a regulated workflow, or the cost is simply not justified for an internal tool that works.
Organisations are then stuck choosing between two bad options: leave legacy apps outside the modern identity perimeter, or build a separate fragile integration for each one. The end result is a fractured user experience, decentralised access control, and identity logic scattered across each backend's own legacy model.
The right approach is to position the modern access layer in front of the app. The user first passes through central identity, MFA, conditional access, and device-trust checks; the authenticated identity is then translated into a shape the backend already understands. The app gets a modern SSO experience without any code change.
But this transition has to be done carefully. If spoofed identity headers from the user are forwarded to the backend without being stripped, anyone can add their own X-Auth-User header to their request and impersonate another user. The gateway must remove untrusted inbound values, inject only the trusted identity it produces itself, and scope this strictly per route.
Logout, session-loss, and backend-driven identity state also have to flow through the same engine. Otherwise the user can appear logged out at the portal while the backend session stays alive, or the backend session can drop without the access layer noticing.
Backend SSO is not about forcing legacy apps to be rewritten — it is about putting modern identity control in front of the app, and safely translating the authenticated user identity into the language the backend already accepts.
How we approach it
One configuration object per backend, five injection shapes, coordinated with the rest of the access engine.
Strip every incoming value, then inject the authenticated one
Every injection rule first strips any inbound copy of the target header, cookie, or Authorization value, then injects the version derived from the authenticated session. A user who sends their own "X-Auth-User" cannot impersonate someone else — their header is removed before the gateway adds the trusted one.
Five injection shapes for the legacy patterns we see
Custom headers (X-Auth-User, X-Forwarded-User, anything the backend expects), Authorization Basic for apps that want a username:password pair, Authorization Bearer for token-friendly backends, Cookie value merge for apps that read a named cookie, and SAML-SP for backends that expect a signed SAML assertion. Each injection has its own configuration; one backend can use several shapes at once.
Per-injection conditions scope each rule to the right routes
Each injection rule carries its own condition expression — only apply on the admin path, only when a specific session attribute is present, only for one tenant. The same backend can receive richer identity on privileged routes and a minimal subset on public ones.
Backend session lost and backend-initiated logout flow back through the gateway
When the backend reports the user's session is gone (a known response shape per service), or when the backend itself logs the user out, AAM detects the signal, cleans up the gateway-side session, and redirects according to the configured policy. Logout-wins precedence ensures cleanup always runs before any further injection.
Capabilities
Five shipping injection shapes, the input-stripping discipline that makes them safe, plus the Windows-estate shapes — Kerberos sign-in at the front, NTLM published transparently at the back.
Header injection — any header name the backend trusts
The simplest and most common shape. Configure a header name (X-Auth-User, X-Forwarded-User, REMOTE_USER, anything the backend reads) and the smart variable that produces the value (username, email, group list, display name). The header is stripped from inbound requests, then re-added from the authenticated session before the request reaches the backend.
Authorization Basic injection for apps that want username:password
For legacy apps that authenticate via HTTP Basic, the gateway injects Authorization: Basic
Authorization Bearer injection for token-aware backends
For backends that already speak Bearer tokens (internal APIs, microservices, modern intranet apps) AAM injects Authorization: Bearer
Cookie value injection with safe single-header merging
Apps that read identity from a named cookie are accommodated with cookie-value injection. The gateway merges the injected name=value pair into the request's Cookie header without clobbering the other cookies — the most error-prone of the four header-style shapes, handled with explicit merge logic rather than naïve overwrites.
SAML-SP injection — a signed SAML assertion per request
For backends that expect a signed SAML assertion on every request, the gateway mints a SAML 2.0 assertion from the authenticated session, signs it with AAM's SAML signing key, and forwards it on the configured header to the backend. Typical for public-sector identity federation integrations and enterprise SaaS backends. The user signs in with a modern IdP; the backend receives a fresh, scoped SAML assertion on every request.
Input-stripping discipline applied to every injected value
Every injection is paired with an inbound strip of the same target. A user who sets X-Auth-User in their own request, who sends a forged Cookie with a tampered identity value, or who replays an Authorization header from somewhere else cannot make the value survive past the gateway. The injection only runs after the strip has cleared the slot.
Per-injection conditions for scope and precedence
Each injection rule can attach a condition — same expression language as conditional access policy. Only inject on the admin path; only inject when the user is in a specific group; only inject the privileged variant when an attribute is present. Conditions are compiled in an ACL-precedence-safe way so multiple rules on one backend compose correctly.
Authenticated-only guard around every injection
All injections are gated behind the authenticated state — they run only when the request carries a valid AAM session. A user who sneaks past authentication via a misconfigured route cannot accidentally receive injected backend credentials; an anonymous request always reaches the backend without injection.
Kerberos sign-in — the Windows user is recognised without a password prompt
A domain-joined browser negotiates SPNEGO with the gateway and the user is signed in with the Windows account they already hold — no second password, no extra portal. From there the backend receives the identity through whichever injection shape it understands, so a legacy application gains silent single sign-on without touching a line of its code. Identity delegation to the backend on the user's behalf is deliberately out of scope: it ties the gateway to a domain trust model that most estates are actively leaving, and the injection shapes cover the same ground with far less blast radius.
NTLM published transparently — the legacy estate keeps working
Older Windows applications that negotiate NTLM are published without modification. The platform recognises the scheme, marks the connection private so it is never shared or rebalanced, and pins it to the server that issued the challenge — which is the requirement that makes NTLM survive a proxy at all. No agent on the server, no rewrite, no exception in the firewall.
Operational depth
The mechanics that make header-grade injection safe at the access edge.
Compile-time precedence-safe condition stacking
Per-injection conditions compose with the gateway's other ACL-driven decisions (authentication state, access policy, backend selection). The condition compiler uses an extra-entries pattern so injection conditions always evaluate after authentication and policy, never before — a per-injection rule cannot accidentally invert the result of a higher-precedence policy decision.
Conditional-frontend variable fetches via dispatcher
When an injection needs a value that is not in the session bag — a value derived from a per-request fetch (group expansion, attribute lookup) — the dispatcher emits a conditional fetch that runs only when the injection's condition matches. Backends that never see an injection never pay the cost of fetching the value.
Backend session-lost detection with configurable response signature
Each service can declare the response signature that means "my session is gone" — a specific status code, a specific response header, a body marker. When the gateway sees that signature, it sets a session-lost flag, cleans up the gateway-side session, and redirects per the configured policy.
Backend-initiated logout cleanup and redirect chain
When the backend itself logs the user out — typically by responding with a known logout signature — the gateway runs a three-step cleanup: clear the gateway-side cookies, delete the session record, and redirect via the configured target. Logout-wins precedence ensures this path beats any in-flight injection.
Encrypted value handling, never logged in cleartext
Credentials and tokens used by injections are stored encrypted in the configuration store and are never written to access logs in cleartext. Audit entries record that an injection ran on a request, not what value it carried. Operators see the policy; the wire payload stays on the wire.
Silent re-auth when the backend session is lost
When a backend drops its own session while the AAM session is still valid, the gateway re-establishes it in the background with a 302 to the AAM daemon instead of bouncing the user to a login page — the user never sees it happen. Logout travels the other way too: signing out of the gateway pushes a logout signal to every backend that received an injected identity, so a closed session is closed everywhere.
Where teams use it
Modern SSO over the existing intranet portal
An internal portal that has trusted X-Remote-User for a decade keeps reading X-Remote-User. The gateway runs modern SAML/OIDC at the edge, then injects the same header it always saw. No backend deploy, no migration cutover.
Authorization-Bearer in front of microservices
A cluster of internal microservices wants Bearer-token authentication but doesn't want to run an identity flow per service. The gateway issues a signed token per request and injects it; each service verifies the token against the gateway's keys.
One sign-on, many backend auth shapes
A multi-app deployment includes one app that wants Basic Auth, one that wants a custom header, and one that wants a cookie. One AAM gateway handles login once; each backend gets the shape it expects, simultaneously, with per-route conditions.
Audit-grade identity propagation
Compliance regimes that require a clear identity chain — "who was authenticated when this request hit the backend" — get that chain produced by the gateway's audit stream. Header values, injection conditions, and the authenticated subject are recorded together.
Common questions
How does this work without changing the backend's code?
What if a user sends their own X-Auth-User header to spoof another identity?
Can the gateway send a signed SAML assertion to a backend that expects one per request?
What happens when the backend session expires but the AAM session is still valid?
Can one AAM gateway run different injection shapes for different backends at the same time?
Modern SSO at the front, legacy auth at the back
We'll deploy backend SSO against your real applications — keeping their existing trust model, removing the credential from the user's hands, and producing an audit-grade identity chain for every request.