Skip to content

Issue 117 Sign In Redirected To The Wrong Address

Ed Mozley edited this page Aug 30, 2026 · 1 revision

Sign-in redirected to the wrong address (issue #117)

Clicking Sign in with SSO sent the browser to the FreeITSM host itself instead of to the identity provider, and produced a 404.

Reported in issue #117 by Kristian Madsen, running a hand-written PHP identity provider behind IIS.

The cause turned out to be on the reporter's side - a reverse proxy rewriting outbound requests. That is worth saying plainly at the top, because the report arrived with a confident and specific accusation against FreeITSM which was wrong, and the way that was settled is the useful part of this page.

Changes shipped anyway as updates #1286, #1287 and #1288, commits 95c3fe95 and f575e24a.


1. What you saw

An analyst clicks the SSO button on the login page. Instead of arriving at the identity provider's sign-in form, the browser lands on the FreeITSM installation's own address with the provider's path stuck on the end, and the web server answers 404.

Nothing on the screen mentions the identity provider. Every visible symptom points at FreeITSM.


2. The report's stated cause, which was false

The issue was written with the help of an AI assistant and stated that FreeITSM "overrides the provider's host with the local application domain."

No such code path exists. FreeITSM builds the authorization URL from one source and one only - the authorization_endpoint published in the provider's own discovery document at /.well-known/openid-configuration. It is used verbatim. Rewriting a provider's own address would itself be a bug, and a nasty one: it would break every legitimate provider that serves its endpoints from a different host than its issuer, which includes Microsoft Entra and Okta.

So the first job was not to argue but to look. The provider's discovery document is public, unauthenticated metadata - the same request the D003 diagnostic makes - and fetching it settled the question in one step:

authorization_endpoint : https://www.zodiacrp.dk/oidc/authorize

Absolute, correct, and pointing at the provider. Whatever was rewriting the address, it was not the discovery document and it was not us.

3. What it actually was

The reporter found it: Application Request Routing, the IIS reverse proxy module, was rewriting outbound requests when it had not been configured to.

That is why the symptom was so misleading. The address FreeITSM emitted was correct. The address the browser followed was not. Nothing inside the application could see the difference, and nothing it could log would have shown it.


4. What was changed anyway, and why

The report described a real failure shape even though it misattributed it, and that shape is worth defending against:

A relative Location: header is resolved by the browser against the current origin.

The OIDC specification requires authorization_endpoint to be an absolute URL, but a misconfigured provider can publish a bare path - /oidc/authorize. FreeITSM used the published value verbatim, so that went straight into:

header('Location: ' . $authUrl);

The browser then resolves /oidc/authorize against the host it is currently on, which is the FreeITSM installation. The user lands on their own service desk, gets a 404, and nothing anywhere implicates the identity provider. It is indistinguishable from the ARR symptom, from the outside.

oidcDiscover() now refuses any of authorization_endpoint, token_endpoint or jwks_uri that is not an absolute http or https URL, and the error names the identity provider as the thing to fix and quotes the offending value.

One detail worth stealing

The validator is deliberately not filter_var($url, FILTER_VALIDATE_URL):

function oidcIsAbsoluteHttpUrl(string $url): bool

FILTER_VALIDATE_URL accepts javascript: and data: URLs. Those must never reach a browser redirect. A validation function that answers "is this a URL" is the wrong question when the real question is "is this safe to put in a Location: header".


5. The diagnostic that named the wrong culprit

D003 prints the health of a self-service SSO provider. Two things came out of using it on this issue.

The first: it reported each endpoint as present or absent. The one fact that decides where the browser goes was the one fact the tool would not state. It now prints the value of each endpoint, masked through maskGuids so an Entra tenant id does not leak into a pasted diagnostic.

The second is the more instructive. D003 compared the issuer typed into FreeITSM against the issuer in the discovery document and reported a mismatch as "this breaks login".

It does not. validateIdToken compares the token's iss claim against $disco['issuer'] - the provider's own declared value - not against the field typed into FreeITSM. So the commonest mismatch of all, www.example.com versus example.com, signs in perfectly well. A different host from the issuer is also entirely legitimate and is now reported as a note rather than a failure, because Entra and Okta both do it.

A diagnostic that names the wrong culprit costs somebody a day, which is precisely the disease this whole issue was about.


6. Files

πŸ—„οΈ schema Β· πŸ“– read Β· ✏️ write Β· πŸ–₯️ UI Β· πŸ§ͺ test

🎨 File What changed
✏️ includes/oidc.php oidcDiscover() rejects non-absolute endpoints; new oidcIsAbsoluteHttpUrl()
πŸ“– api/system/debug-tools/D003_selfservice_sso.php Prints endpoint values; issuer mismatch demoted from blocker to note; protocol shown and schema-checked
πŸ§ͺ tests/oidc-discovery.php 27 assertions, mostly negative cases

7. How it was verified

The mechanism was reproduced end to end rather than reasoned about. A directory under the web root served a deliberately malformed .well-known/openid-configuration, with a temporary auth_providers row pointing at it. With the guard disconnected the endpoint really does emit:

Location: /oidc/authorize?client_id=...

Both the fixture and the temporary row were removed afterwards.

The test was proved load-bearing by disconnecting the guard - 26 passing and 1 failing, then 27 passing when restored. A test that has never been seen to fail has not been shown to test anything.


8. What this means for you

  • If SSO sends you to your own FreeITSM address, suspect the path between the browser and the provider before suspecting FreeITSM. A reverse proxy that rewrites outbound requests - IIS ARR, nginx proxy_redirect, a load balancer - can do this invisibly.
  • If your provider publishes a relative endpoint, FreeITSM now refuses it by name at the point of discovery instead of bouncing you to a 404.
  • Run D003 from System β†’ Debug tools. It now prints the addresses it found, which is usually enough to see the answer without reading any code.

9. Related

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally