> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# The OAuth Login That Broke on the Trailing Slash
- URL: https://debugly.dev/oauth-redirect-trailing-slash/
- Published: 2026-10-09T17:43:00.000Z
- Updated: 2026-10-10T13:58:55.000Z
- Description: The redirect URI was registered without the slash and the app sent it with one, so the provider rejected the match, and every login failed with a generic…
- Author: Rohit Bhadani
- Tags: Error Autopsy, Authentication, OAuth

The failed behaved. The rejection behaved. The chain between them is where working as designed stopped meaning working.

The exact string match on a URL has a single weak property, and here it is: the provider compares the redirect URI character by character, so one differing character fails every login for every user at the same moment.

This was an OAuth 2.0 authorization code flow, and the mechanics are the same for any provider that validates the redirect URI against a registered list.

## Why the match was exact

The redirect URI is the security's anchor, and the anchor is the exact match, and the exact is the not the prefix, and the not prefix is the reason, because the prefix match would let the attacker register a longer path, and the longer path is the open redirect, and the open redirect is the token's theft, and the theft is the incident, so the exact match is the correct design and the correct design is the brittle.

The registered was the callback without the slash, and the sent was the callback with the slash, and the two were the different strings, and the different strings were the mismatch, and the mismatch was the rejection, and the rejection was the error, and the error was the generic, and the generic was the diagnosis' delay, and the delay was the hour, and the hour was the outage's duration.

The exact match is the correct and the brittle, and the brittle is the reason the URI should be the single source of truth, and the source is the configuration, and the configuration is the one place, and the one place is the fix, and the fix is the discipline, because the two places that must agree are the two places that drift.

## Why the error named nothing

The provider's error was the redirect\_uri\_mismatch, and the mismatch was the fact, and the fact was the not the difference, and the not difference was the security, because the detailed error would help the attacker probe the registered values, and the probe is the enumeration, and the enumeration is the risk, and the risk is the reason the error is vague, and the vague is the diagnosis' cost.

The diagnosis was the comparison, and the comparison was the two strings, and the two strings were the registered and the sent, and the sent was the browser's address bar, and the address bar was the visible, and the visible was the slash, and the slash was the answer, and the answer was the ten seconds once someone thought to compare, and the compare was the not the first thought, and the not first was the hour.

This is the same silent mismatch as [the DNS TTL that was still an hour during the migration](https://debugly.dev/dns-ttl-still-an-hour/), and the common root is the property, not the platform.

## The fix, in order

**Made the redirect URI a single configured constant.** The value was the one environment variable, and the variable was the registered's copy, and the copy was the agreement, and the agreement was the fix, and the fix was the source of truth, and the source was the discipline, because the hardcoded string in the code and the string in the provider's console are the two places that drift.

**Normalised nothing and matched exactly, deliberately.** The exact was the required, and the required was the explicit, and the explicit was the test, and the test was the comparison, and the comparison was the guard, and the guard was the fix, and the fix was the assertion, because the normalisation hides the mismatch until the provider rejects it.

**Tested the full flow against the provider in CI.** The test was the real authorization request, and the request was the mismatch's catch, and the catch was the prevention, and the prevention was the fix, and the fix was the integration test, and the test was the discipline, because the unit test with the mocked provider cannot catch the registered value's drift.

**Logged the sent redirect URI on the failure.** The logged was the comparison's input, and the input was the diagnosis, and the diagnosis was the seconds, and the seconds was the fix, and the fix was the one line, and the line was the discipline, because the vague provider error needs the local value to be comparable.

**Registered both variants where the provider allowed it.** The two were the belt and the braces, and the two were the tolerance, and the tolerance was the fix, and the fix was the console's entry, and the entry was the mitigation, and the mitigation was the not the solution, because the single source is the solution.

## The takeaway

The OAuth redirect URI is compared as an exact string, so a trailing slash, a scheme difference or a port difference fails every login at once. Keep the URI in one configured constant, test the real flow against the provider in CI, log the sent value on failure, and never normalise the value before comparing.

Every login on the product failed at the same minute because the redirect URI in the code had a trailing slash and the one registered in the provider's console did not, and the provider's error said only that they did not match. The lesson I keep is the property, not the incident.