The short answer
You cannot satisfy both constraints with a single registered redirect URI value, and you should not try. The standard practice is to register separate OAuth clients for development and production: the dev client carries http://localhost:8080/callback, and the production client carries the exact HTTPS URI. On PKCE: enforce it for all new clients, including confidential ones, and migrate legacy confidential clients on a staged timeline rather than leaving the exception open-ended.
Why one client can't serve both environments
Under RFC 6749, redirect URIs must be pre-registered and matched exactly — scheme, host, port, path, and trailing slash all count. Production-grade providers additionally require HTTPS for registered redirect URIs, with a deliberate exception for loopback addresses (http://localhost, http://127.0.0.1) so developers can test without TLS certificates. That exception is a development concession, not a production feature: many providers will reject or flag a loopback URI on a client that looks production-bound, and shipping it is a security risk regardless.
The common "works locally, fails in production" symptom is almost always a redirect_uri_mismatch caused by one differing character — a missing trailing slash, localhost vs 127.0.0.1, or a port the provider treats as part of the match. (Native apps under RFC 8252 are the exception: loopback redirects there use an ephemeral port and the provider ignores the port when matching.)
Recommended setup
- Create two client registrations:
myapp-dev and myapp-prod. - Register only loopback URIs on the dev client; only the exact HTTPS URI (including trailing slash and any query string) on the prod client.
- Select the client ID and redirect URI from environment configuration, never from branching logic in code.
- Never enable wildcard or broad-domain redirect matching in production — it undermines the exact-match protection and can enable authorization-code theft via open redirects.
Note that limits on the number of redirect URIs per client, wildcard support, and IP-based hostnames are provider policy (Google, Microsoft Entra, Auth0, Okta all differ), not OAuth spec requirements — check your provider's console.
PKCE for confidential clients
Yes — enforce PKCE in production even for confidential clients. The client secret and PKCE protect against different failures: the secret authenticates the client at the token endpoint, while PKCE binds the token exchange to the specific authorization request, blunting code-interception and code-injection attacks (for example via a compromised redirect or a leaked log). Current OAuth security best-practice guidance recommends PKCE for all client types, and the cost for a confidential client is small: generate a code_verifier, send its SHA-256 hash as code_challenge, and present the verifier at token exchange.
Migrating legacy clients that can't do PKCE
- Inventory: log which client IDs submit token requests without a
code_verifier. Most providers or your gateway can surface this. - Enforce by default, except by list: require PKCE for all new registrations immediately; keep a named allowlist of legacy client IDs that are temporarily exempt.
- Compensating controls for exempt clients: keep client secrets rotated, restrict redirect URIs to a single exact value, and shorten authorization-code lifetime where configurable.
- Deadline and owner: assign each exempt client a migration owner and a removal date; an exception without an expiry date becomes permanent.
- Verify before cutoff: run a staging flow with PKCE enabled and confirm the token exchange succeeds before flipping the production flag.
Verifying your configuration
Trigger an authorize request with the exact production redirect URI and confirm it is accepted; then change one character and confirm you get a redirect_uri_mismatch-style error. Locally, test both localhost and 127.0.0.1 to learn which forms your provider treats as equivalent. One caveat: exact loopback handling and per-client URI limits vary by provider and change over time, so confirm against your provider's current documentation before locking the design.