Aller au contenu principal
Version 0.3Brouillon

Redirect and domain issues

LuxID applies a strict exact-match policy to redirect URIs (OIDC) and ACS URLs (SAML). The policy is a deliberate security boundary: it prevents open-redirect attacks, response-splitting tricks, and downstream credential leakage. The trade-off is that every cosmetic mismatch fails closed. This page is the catalogue of the mismatches we see most often and the precise fix for each.

Why exact match

When you receive an authorization response at your redirect URI, you also receive the user's credentials in the form of an authorization code (or in SAML, the assertion). If LuxID accepted approximate matches (https://app.example/cb* or any path under app.example), an attacker who registered the same Application under their control could craft a URL that captured legitimate users' codes. Exact match closes that hole.

The same rule applies to logout post-redirects, mobile deep-link schemes, and SAML Assertion Consumer Service URLs.

The error

When LuxID rejects the redirect URI, the user sees a LuxID error screen showing an on-screen identifier you can quote to support (see the Identifiers reference), and your Application receives:

error=invalid_request
error_description=The redirect_uri MUST exactly match a value registered for this client.
attention

For security reasons LuxID does NOT redirect back to your supplied URI when the URI itself is the problem - the user lands on a LuxID-hosted error page instead. This is intentional.

Common failures

Trailing slash mismatch

Registered: https://app.example/callback

Sent in redirect_uri: https://app.example/callback/

LuxID treats these as different URIs. Pick one canonical form, register it, and make sure every code path in your Application emits the same string.

Fix: pick the form your framework actually generates and register exactly that. Most server-side frameworks normalise without a trailing slash; most SPA frameworks normalise with.

Http vs https

Production requires https. LuxID rejects any production redirect URI that uses http://.

http://localhost is accepted only in UAT, and only when the host is literally localhost (not 127.0.0.1, not 0.0.0.0).

Fix: switch your local development to UAT and use http://localhost:<port>/callback. Switch to https:// everywhere for staging and production.

Port mismatch

Registered: http://localhost:3000/callback

Sent: http://localhost:3001/callback

If your dev server picks a free port at startup, every developer on the team ends up on a different port. LuxID does not accept wildcards or port ranges.

Fix: register every port you actively use (3000, 3001, 3002, 5173 for Vite, 8080, 8081, 19000 for Expo). Some teams centralise on a single port and configure dev servers to fail rather than fall back to another.

Wildcard expectation

LuxID does NOT support wildcards. There is no https://*.app.example/callback, no https://app.example/*, no https://app.example/callback?*. Every redirect URI must be enumerated.

Fix: register each redirect URI by hand. If you have many subdomains for tenant isolation, consider a single auth domain pattern (one redirect URI to a host that then forwards to the tenant) rather than per-tenant URIs.

The app developer adds com.example.myapp://callback to the iOS Info.plist or the Android AndroidManifest intent-filter, but forgets to register the same scheme with LuxID as a redirect URI on the Application.

Fix: register every scheme used by your native app. Custom schemes follow the same exact-match rule. Universal Links / App Links are also accepted - register the full https://app.example/.well-known/luxid-callback URL.

Query parameters on the registered URI

LuxID treats the registered redirect URI as a stable identifier. If you registered https://app.example/cb?env=prod, the runtime URI sent must include the exact same query string, byte for byte. Any standard OAuth parameters that LuxID adds to the response (code, state, iss) are added AFTER the registered URI, never to it.

Fix: avoid query parameters in registered URIs. If your application needs to convey environment or tenant context, put it in state instead. state is opaque to LuxID and round-trips back to you intact.

Mixed-environment URIs

Registered the production Application's URI as https://app.example/cb, registered the UAT Application's URI as https://app-uat.example/cb. Then accidentally sent the UAT URI against the production Client ID, or vice versa.

Fix: keep your environment configuration tightly scoped. Use separate environment variables for LUXID_CLIENT_ID, LUXID_CLIENT_SECRET, and LUXID_REDIRECT_URI, and load the right set per environment. Do not derive one from the other.

Encoded vs raw characters

Registered: https://app.example/path%20with%20space/cb

Sent: https://app.example/path with space/cb (raw spaces)

LuxID compares the URI byte for byte after standard URI normalisation. Spaces, brackets, and other reserved characters must be percent-encoded in both the registered URI and the runtime URI.

Fix: avoid special characters in redirect paths. If you must use them, percent-encode consistently and test in UAT before going live.

Subdomain mismatch

Registered: https://www.app.example/cb

Sent: https://app.example/cb (no www)

LuxID treats subdomains as distinct hosts. www.app.example is not the same as app.example.

Fix: pick a canonical host and redirect (HTTP 301) the other to it BEFORE the OAuth flow begins. The user must reach LuxID with the canonical URI as redirect_uri.

SAML ACS URL: the same rules

The SAML Assertion Consumer Service URL behaves the same way as a redirect URI. The ACS URL in your SP metadata MUST match exactly the URL registered against the Application with LuxID. The same rules apply: HTTPS in production, exact host and path, no wildcards.

If your SP supports multiple ACS URLs (different bindings or different services), register each one explicitly.

SAML SP-initiated AuthnRequest mismatch

For SAML, the LuxID IdP looks up your registered ACS URL based on the Issuer element of the AuthnRequest, not on the AssertionConsumerServiceURL attribute. If your AuthnRequest carries an AssertionConsumerServiceURL that does not match any registered ACS URL for the issuing SP, LuxID rejects the request.

Fix: leave AssertionConsumerServiceURL out of the AuthnRequest and let LuxID pick the default ACS URL registered for the SP. Alternatively, send a value that exactly matches one of the registered ACS URLs.

Domain ownership verification

For new redirect URIs on a custom domain, LuxID requires domain ownership verification before activating the URI in production. The verification challenge is one of:

  • DNS TXT record: _luxid-challenge.app.example. with a token value provided by LuxID.
  • HTTPS file challenge: https://app.example/.well-known/luxid-challenge.txt containing the token.

LuxID's verification poller checks the challenge every few hours and activates the URI once the challenge is observed. Once verified, the domain stays verified until you explicitly remove it or change the apex DNS configuration.

See Redirect URIs and domains for the field reference.

A diagnostic flow

Checklist before going to production

  • Every production redirect URI uses HTTPS.
  • Every redirect URI is registered byte-for-byte with LuxID.
  • No wildcards anywhere.
  • Mobile deep-link schemes are registered alongside web redirect URIs.
  • Sign-out clears the local application session. (LuxID does not expose an OIDC end_session_endpoint / RP-initiated logout, so there is no post_logout_redirect_uri to register.)
  • SAML ACS URLs in SP metadata match the URLs in the Console.
  • Domain ownership has been verified for any custom-domain URI.
  • UAT and Production Applications have distinct redirect URIs (no overlap).

Cross-references

Mise à jour le 2026-07-03