
- Register complete redirect URIs and compare them as exact strings.
- Allow a changing port only for the native-app loopback case defined by the standard.
- Use authorization code flow with PKCE. Keep the verifier tied to one login attempt.
An OAuth login can look normal and still send its authorization code to the wrong place. The problem often starts with a convenient callback rule: any subdomain, any path below a prefix, or a loose regular expression.
That flexibility creates more destinations than the team meant to trust.
Use two gates. First, the authorization server must match the requested redirect URI to a registered value. Next, PKCE must bind the code exchange to the client instance that began the request. One control does not replace the other.
Make the redirect list boring
Register every callback as a complete URI. Include the scheme, host, port when fixed, path and query string when your provider supports one. Keep production and preview environments in separate client records where practical.
At the authorization endpoint, use simple string comparison. Reject host suffixes. Do not accept an added path, a new query value, or a look-alike subdomain.
RFC 9700 makes exact string matching the current best practice. The rule helps prevent authorization codes and tokens from leaking through weak patterns. Open redirectors are banned.
| Requested redirect | Registered value | Decision |
|---|---|---|
https://app.example/callback | Same complete URI | Continue |
https://preview.app.example/callback | Production URI only | Reject |
https://app.example/callback/next | Shorter path | Reject |
https://app.example/callback?next=evil | No query | Reject |
These are labelled examples, not a report about a tested provider. Some platforms add their own registration rules, so read the provider document before release.
Keep return navigation out of the callback URI
Teams often add a next URL to send a user back to the page they wanted. Reject that pattern.
Instead, store the intended route in server-side session state. You may also bind an opaque, one-time reference to the browser session. After the callback passes, resolve that reference to a small allowlist of local routes.
This keeps the registered callback stable. It also makes the open-redirect review much simpler. The callback page still needs normal browser controls; the Content Security Policy starting guide covers one useful layer.
Add PKCE as a separate proof
Before the authorization request, the client creates a high-entropy verifier. It sends a derived challenge with the request and keeps the verifier for the token exchange.
code_challenge = BASE64URL(SHA256(code_verifier))
code_challenge_method = S256
The server binds it to the code. Later, it issues tokens only when the presented verifier produces the same challenge. A party that sees only the code does not have the missing proof.
RFC 9700 requires public clients to use PKCE and recommends it for confidential clients. It says the advice applies to web apps too. Authorization servers must support PKCE, and S256 is the current method that does not expose the verifier in the request.
Keep each verifier transaction-specific. Do not reuse a fixed value across logins. Protect it like temporary login state, and clear it after success or expiry.
Do not erase the native-app exception
Desktop apps may open a temporary listener on a loopback IP address. They cannot always know the port before startup.
RFC 8252 therefore allows the port to vary for loopback redirects. The app should use an IP literal such as 127.0.0.1 or [::1], listen only on loopback, open the port for the request, and close it after the response.
This is a narrow exception. It is not permission to wildcard a web callback host or path. Claimed HTTPS links are preferred for native apps where the platform can bind them to the app.
Test rejection before launch
- Start with the registered URI. Confirm the authorization request reaches the expected callback.
- Change one part. Try the scheme, host, path and query separately. Each unregistered value should fail before a code is sent.
- Break the verifier. Use a wrong or missing PKCE verifier at the token endpoint. The code exchange must fail.
- Replay the code. A used code must not produce another token.
- Check return navigation. An external or malformed destination must not become an open redirect.
Run these checks in your own test tenant and save the results. TechWeb did not test your authorization server, client library or identity provider.
After tokens exist, other controls take over. The JWT signing-key rotation guide covers verification keys. The device-bound session guide addresses stolen bearer sessions. For a purchased identity service, add exact redirect matching and PKCE behavior to the vendor security evidence review.
Frequently asked questions
Can an OAuth redirect URI use a wildcard?
Current OAuth security guidance calls for exact string matching, not wildcard or pattern matching. The port in a native-app loopback redirect is the defined exception.
Does PKCE replace exact redirect URI matching?
No. Exact matching controls the destination. PKCE binds the later code exchange to the client instance that started the request.
Should localhost ports match exactly?
Native apps using a loopback redirect may choose an available port. Use the loopback IP form and keep the rest of the registered URI controlled.
Sources
IETF RFC 9700: OAuth 2.0 Security Best Current Practice · IETF RFC 8252: OAuth 2.0 for Native Apps · IETF RFC 7636: Proof Key for Code Exchange · IETF RFC 6749: OAuth 2.0. Provider defaults can change; verify the exact product and client type you run.
More in this sectionTechnology →