Nurture TechnologiesNurture Tech
Integrations8 min read·July 24, 2026

Auth0 Callback URL Mismatch in Production

Auth0Next.jsNode.js

An Auth0 application that works perfectly in development starts throwing 'callback URL mismatch' errors after deploying to production. The fix is in the Auth0 dashboard Allowed Callback URLs list, but the details matter: HTTP vs HTTPS, trailing slashes, environment-specific configuration, and wildcard URL gotchas all contribute to the problem.

Problem Summary

A Next.js application using Auth0 for authentication is deployed to a production domain. In development, auth works correctly at http://localhost:3000. After deployment to https://myapp.com, users who attempt to log in are redirected to Auth0 and then redirected back to the application, but Auth0 throws an error before completing the callback. The error page at Auth0's login domain (your-tenant.auth0.com) shows 'Oops, something went wrong callback_url_mismatch.' The URL in the Auth0 dashboard still has localhost:3000 as the only allowed callback URL.

Symptoms

  • Auth0 error page: 'error=access_denied&error_description=callback_url_mismatch' in the redirect URL
  • Error message: 'The redirect URI in the authorization request does not match any registered redirect URIs'
  • The login flow works for developers (on localhost) but fails for all production users
  • Auth0 logs in the Dashboard show 'Failed Login: Callback URL mismatch' events
  • The application's AUTH0_BASE_URL environment variable points to the production URL but the Auth0 dashboard still has localhost
  • Wildcard callback URL attempts like https://*.myapp.com are not matching as expected
  • HTTPS production URLs fail but an HTTP version of the same domain (or a redirect chain) causes the URL Auth0 receives to not match the registered URL
  • Staging and production deployments share the same Auth0 application and one environment's working config breaks the other

Business Impact

A callback URL mismatch is a hard authentication failure. No user can log in to the production application. New user sign-ups are impossible. Existing users cannot access their accounts. The application is effectively offline for all authenticated features. Depending on how quickly the issue is identified, this can represent hours of downtime during a product launch or after a routine deployment.

Root Cause

Auth0 implements the OAuth 2.0 redirect_uri validation as a security measure. When your application initiates a login flow, it sends a redirect_uri parameter telling Auth0 where to send the user after authentication. Auth0 compares this value against the list of Allowed Callback URLs registered in the application's settings. If the value sent by the application does not exactly match one of the registered URLs including protocol (http vs https), port, path, trailing slash, and query string Auth0 rejects the request with callback_url_mismatch.

Auth0's Allowed Callback URLs comparison is exact string matching, not prefix or pattern matching. https://myapp.com/api/auth/callback and https://myapp.com/api/auth/callback/ (with a trailing slash) are treated as different URLs. http://myapp.com/api/auth/callback and https://myapp.com/api/auth/callback are different URLs. The URL your application sends must be an exact byte-for-byte match of one entry in the allowed list.

Diagnosis

Step 1: Find the Exact Callback URL Your App Is Sending

The easiest way to find the exact redirect_uri your application is sending to Auth0 is to look at the Auth0 Dashboard logs. Navigate to Auth0 Dashboard > Monitoring > Logs and filter for 'Failed Login' events. Click a failed login event and look at the 'data.redirect_uri' field this is the exact URL that your application sent and that did not match.

Auth0 Management API fetch recent failed logins
# Using Auth0 Management API to fetch recent logs
# Replace YOUR_DOMAIN and YOUR_MGMT_TOKEN

curl -X GET "https://YOUR_DOMAIN.auth0.com/api/v2/logs?type=f"   -H "Authorization: Bearer YOUR_MGMT_TOKEN"   -H "Content-Type: application/json" | jq '.[0].data.redirect_uri'

# Or use the Auth0 CLI:
auth0 logs list --filter type:f --number 5

Step 2: Update Allowed Callback URLs in the Auth0 Dashboard

Navigate to Auth0 Dashboard > Applications > Your Application > Settings. Find the 'Allowed Callback URLs' field. This field accepts a comma-separated list of URLs. Add your production callback URL exactly as it appears in the Auth0 logs from Step 1.

Auth0 Dashboard Allowed Callback URLs field
# Add all environments you need to support
# Comma-separate each URL on the same line or separate lines

http://localhost:3000/api/auth/callback,
https://myapp.com/api/auth/callback,
https://staging.myapp.com/api/auth/callback

# For Next.js with auth0-nextjs-sdk (v3+), the callback path is:
# /api/auth/callback

# For Next.js with @auth0/nextjs-auth0 (older), it may be:
# /api/auth/callback/auth0 (if using NextAuth.js under the hood)

# Also update these fields in the same Settings page:
# Allowed Logout URLs:
http://localhost:3000,https://myapp.com,https://staging.myapp.com

# Allowed Web Origins (for silent auth / refresh tokens):
http://localhost:3000,https://myapp.com,https://staging.myapp.com

Step 3: Verify Environment Variables Match the Registered URL

The auth0-nextjs-sdk constructs the redirect_uri from your AUTH0_BASE_URL environment variable. If AUTH0_BASE_URL is set to http://myapp.com (HTTP) but your application is served over HTTPS, the redirect_uri sent to Auth0 will use HTTP, which will not match an HTTPS entry in the allowed list. Check that AUTH0_BASE_URL uses the same protocol as your production URL.

.env.production
# Auth0 SDK environment variables
AUTH0_SECRET=your-long-random-secret-min-32-chars
AUTH0_BASE_URL=https://myapp.com       # Must use HTTPS in production
AUTH0_ISSUER_BASE_URL=https://your-tenant.auth0.com
AUTH0_CLIENT_ID=your-auth0-client-id
AUTH0_CLIENT_SECRET=your-auth0-client-secret

# The SDK constructs the callback URL as:
# AUTH0_BASE_URL + "/api/auth/callback"
# So with the above: https://myapp.com/api/auth/callback
# This must appear in Auth0 Dashboard > Allowed Callback URLs

Step 4: Handle Wildcard URLs for Preview Deployments

Vercel creates unique preview URLs for each deployment (myapp-git-branch-team.vercel.app). You cannot pre-register all possible preview URLs. Auth0 supports wildcard entries in Allowed Callback URLs but with important restrictions: wildcards can only appear at the beginning of the URL and only as a subdomain wildcard.

Auth0 wildcard URL examples
# VALID wildcard  matches any subdomain of vercel.app ending in myapp
https://*.vercel.app/api/auth/callback

# VALID  matches myapp-*.vercel.app
https://myapp-*.vercel.app/api/auth/callback

# INVALID  wildcards cannot appear in the path
https://myapp.vercel.app/*/auth/callback

# INVALID  wildcards cannot appear in the protocol or domain TLD
https://*myapp*/api/auth/callback

# For Vercel specifically, use a more targeted pattern:
# Auth0 recommends: https://myapp-git-*-myteam.vercel.app/api/auth/callback
# where myteam is your Vercel team slug

For production applications, never use wildcard callback URLs in production Auth0 applications. Use wildcards only in a separate development or staging Auth0 application. Production applications should have an explicit, finite list of allowed callback URLs.

Step 5: Use Separate Auth0 Applications per Environment

The cleanest long-term solution is a separate Auth0 Application (client ID / client secret pair) for each environment: development, staging, and production. Each application has its own Allowed Callback URLs list with no overlap. This prevents production deployments from accidentally being broken by development configuration changes.

Environment-specific Auth0 configuration
# .env.development.local
AUTH0_CLIENT_ID=dev_client_id_here
AUTH0_CLIENT_SECRET=dev_client_secret_here
AUTH0_ISSUER_BASE_URL=https://your-tenant.auth0.com
AUTH0_BASE_URL=http://localhost:3000

# .env.staging (Vercel environment: Preview)
AUTH0_CLIENT_ID=staging_client_id_here
AUTH0_CLIENT_SECRET=staging_client_secret_here
AUTH0_ISSUER_BASE_URL=https://your-tenant.auth0.com
AUTH0_BASE_URL=https://staging.myapp.com

# .env.production (Vercel environment: Production)
AUTH0_CLIENT_ID=prod_client_id_here
AUTH0_CLIENT_SECRET=prod_client_secret_here
AUTH0_ISSUER_BASE_URL=https://your-tenant.auth0.com
AUTH0_BASE_URL=https://myapp.com

Production Failure Scenarios

Scenario 1: Custom Domain Added Without Updating Callback URLs

The app is initially deployed to myapp.vercel.app and Auth0 has that URL in Allowed Callback URLs. The team adds a custom domain myapp.com, updates DNS, but forgets to add https://myapp.com/api/auth/callback to Auth0. The application now serves traffic on the custom domain but Auth0 still only knows about the Vercel URL. Every login from the custom domain fails with callback_url_mismatch. Fix: any time the application's domain changes, Auth0 Allowed Callback URLs, Allowed Logout URLs, and Allowed Web Origins must all be updated.

Scenario 2: Load Balancer Strips HTTPS, App Reports HTTP

A Node.js application behind a load balancer receives requests as HTTP internally (the LB terminates HTTPS). The Auth0 SDK reads req.protocol to construct the redirect_uri. req.protocol returns 'http' because the app sees the internal HTTP request. Auth0 receives http://myapp.com/api/auth/callback, which does not match the registered https://myapp.com/api/auth/callback. Fix: configure the application to trust the X-Forwarded-Proto header from the load balancer by setting app.set('trust proxy', true) in Express or the equivalent in your framework.

Express trust proxy for HTTPS detection
import express from "express";
const app = express();

// Trust the X-Forwarded-Proto header from your load balancer
// so req.protocol correctly returns "https" even though the app
// receives HTTP internally
app.set("trust proxy", true);

// For the Auth0 SDK, also set:
// AUTH0_BASE_URL=https://myapp.com (with HTTPS)
// The SDK will use this value rather than computing from req.protocol

Scenario 3: Trailing Slash Mismatch from Reverse Proxy

A reverse proxy adds a trailing slash to the callback URL (https://myapp.com/api/auth/callback/). The Auth0 dashboard has https://myapp.com/api/auth/callback (no trailing slash) registered. Auth0's exact match comparison rejects the request. Fix: either normalize the URL in the reverse proxy configuration to strip trailing slashes, or add both variants to the Auth0 Allowed Callback URLs list.

Prevention Checklist

  • Add production callback URLs to Auth0 Allowed Callback URLs as part of the deployment runbook, not after deployment
  • Use separate Auth0 Applications for development, staging, and production environments
  • Store AUTH0_BASE_URL in environment-specific .env files and verify it uses HTTPS in production
  • Update Allowed Callback URLs, Allowed Logout URLs, and Allowed Web Origins simultaneously when adding a new domain
  • Set up trust proxy configuration on any Node.js server behind a load balancer that terminates HTTPS
  • After every domain change (custom domain, CDN, etc.), test the login flow from an incognito window immediately
  • Monitor Auth0 Dashboard Logs for 'Failed Login' events and set up alert notifications via Auth0 Log Streams
  • Document all registered callback URLs and which environments they belong to in your team's infrastructure documentation

Resolution Summary

The production login failure was caused by AUTH0_BASE_URL being set to the production HTTPS domain while the Auth0 dashboard Allowed Callback URLs still contained only http://localhost:3000/api/auth/callback from initial development setup. Adding https://myapp.com/api/auth/callback to the Allowed Callback URLs in the Auth0 dashboard along with https://myapp.com to Allowed Logout URLs and Allowed Web Origins resolved the mismatch immediately. No code change was required; it was a configuration-only fix.

Lessons Learned

  • Auth0 callback URL validation is exact string matching protocol, host, port, path, and trailing slash must all match exactly
  • The Auth0 dashboard Allowed Callback URLs field is a deployment-time artifact that must be treated the same as environment variables update it as part of every deployment to a new domain
  • A reverse proxy or load balancer that terminates HTTPS changes the protocol seen by the application and can cause URL mismatches unless trust proxy is configured
  • Separate Auth0 Applications per environment eliminate an entire category of cross-environment configuration interference
  • Auth0 Dashboard > Monitoring > Logs shows the exact redirect_uri that failed always start diagnosis there, not in application code

Conclusion

Auth0 callback URL mismatches are frustrating because the fix is trivial once identified add the URL to the allowed list but the diagnosis can take time if you do not know where to look. The Auth0 Dashboard logs show the exact URL that failed within seconds. From there, the fix is a one-minute dashboard update. The real investment is in preventive infrastructure: separate Auth0 apps per environment and a deployment runbook that includes updating Auth0 configuration alongside environment variables.

Nurture Technologies

NEED HELP WITH YOUR STACK?

Nurture Technologies builds and maintains production-quality software for startups and businesses. If engineering problems are slowing you down, our team can help.

Talk to our team →
FAQ

FREQUENTLY ASKED QUESTIONS

Where exactly do I add the production callback URL in Auth0?+

Auth0 Dashboard > Applications > [Your Application] > Settings > scroll to 'Application URIs' section > 'Allowed Callback URLs' field. This field accepts a comma-separated or newline-separated list of URLs. Add your production URL in the exact format your application sends it: https://myapp.com/api/auth/callback. Click Save Changes at the bottom of the page. Changes take effect immediately no redeploy required.

Why does Auth0 use exact URL matching instead of prefix matching?+

Exact matching is an OAuth 2.0 security requirement defined in RFC 6749. Open redirect attacks exploit loose redirect_uri matching if Auth0 allowed prefix matching, an attacker could register a malicious URL (https://myapp.com.evil.com/api/auth/callback) that starts with the correct prefix and redirect auth tokens to it. Exact matching prevents this class of attack. The strictness is intentional security behavior, not an implementation oversight.

Can I use wildcards in Auth0 Allowed Callback URLs for Vercel preview deployments?+

Yes, but with restrictions. Auth0 supports wildcards only at the beginning of the URL as subdomain wildcards. For Vercel preview URLs, use https://myapp-git-*-myteam.vercel.app/api/auth/callback where myteam is your Vercel team slug. Never use wildcards in production Auth0 applications restrict wildcard patterns to development or staging applications only.

My AUTH0_BASE_URL is correct but Auth0 still shows a mismatch what else can cause it?+

Four other causes: (1) a reverse proxy or load balancer is rewriting the URL before Auth0 receives it, (2) the application has a redirect before the callback that changes the URL, (3) a trailing slash added by the proxy does not match the registered URL, (4) the AUTH0_CALLBACK environment variable (if you have set it explicitly) overrides the computed callback URL. Check the exact redirect_uri in Auth0 Dashboard Logs first it will show you exactly what URL your app sent.

Do I need to update Allowed Logout URLs and Allowed Web Origins too?+

Yes. When you add a new domain: Allowed Callback URLs controls where Auth0 redirects after login; Allowed Logout URLs controls where Auth0 redirects after logout (your returnTo parameter); Allowed Web Origins controls which domains can make cross-origin Auth0 SDK calls (required for token renewal and silent auth). Updating Allowed Callback URLs alone is often not enough users will log in but see a mismatch error when they log out or when the application tries to silently refresh the token.

How do I test Auth0 configuration changes without deploying code?+

Auth0 configuration changes in the dashboard take effect immediately for new login attempts no redeploy of your application is needed. After saving the Allowed Callback URLs change, open an incognito/private browser window, navigate to your application, and attempt to log in. The change either works or the Auth0 error page gives you the exact mismatch information. You can iterate on dashboard configuration quickly without any code changes.

Should staging and production share the same Auth0 application (client ID)?+

No. Use separate Auth0 Applications for each environment. Sharing a client ID creates a single configuration that must satisfy all environments simultaneously. A developer changing the staging callback URL can accidentally break production. Separate applications also allow different token lifetimes, grant types, and connection configurations per environment. The cost of creating an additional Auth0 application is zero create one per environment during initial project setup.

What is the AUTH0_SECRET environment variable and what happens if it is wrong?+

AUTH0_SECRET is used by the auth0-nextjs-sdk to encrypt the session cookie. It must be at least 32 characters long and should be a cryptographically random string. If AUTH0_SECRET is wrong or missing, the SDK cannot create or read session cookies and every request appears unauthenticated. This manifests as users being logged out immediately after a successful Auth0 callback, not as a callback URL mismatch. Generate it with: openssl rand -hex 32.

Can I use Auth0 with Next.js App Router?+

Yes. The @auth0/nextjs-auth0 v3 SDK supports the App Router. The route handlers live at app/api/auth/[auth0]/route.ts. The AUTH0_BASE_URL and other environment variables remain the same. The session is accessed via the getSession() function in server components and route handlers. Review the Auth0 Next.js SDK v3 migration guide if upgrading from v2, as the API changed significantly.

How do I rotate Auth0 client secrets without causing a login outage?+

Auth0 supports adding a new client secret before removing the old one. In Application Settings, generate a new client secret, update AUTH0_CLIENT_SECRET in your application's environment variables, deploy the application, then remove the old client secret from Auth0. This zero-downtime rotation ensures no requests are rejected during the transition. Sessions signed with the old secret remain valid until they expire naturally.