FeaturesGuidesBlogDocumentationFreeGet Started

OAuth Callback on Localhost (Step-by-Step)

Register redirect URIs, run npx localtolink, and complete the auth code flow against your local handler — procedural checklist.

The OAuth localhost problem

OAuth providers (Google, GitHub, Auth0, Microsoft, etc.) redirect users back to your application after authentication. The redirect URI must match exactly what you registered — scheme, host, path, and trailing slash.

Many providers accept http://localhost:3000/callback in development mode. Others require HTTPS and a publicly reachable URL, especially when testing production-like flows, mobile OAuth, or provider apps that reject localhost entirely.

A development tunnel gives you a public HTTPS URL that forwards to your local OAuth callback handler.

Solution: tunnel + redirect URI

1. Start your app with the OAuth callback route implemented:

npm run dev

2. Start LocalToLink:

npx localtolink --port 3000

3. Register the redirect URI in your OAuth provider's console:

https://k7m2x9p4q1.localtolink.com/auth/callback

Use the exact subdomain LocalToLink prints. It changes on each new session.

4. Initiate the OAuth flow by visiting your app through the tunnel URL (not localhost) so redirect URIs match.

Redirect URI rules

OAuth providers enforce strict matching:

RuleExample
Scheme must matchhttps:// not http:// when registered as HTTPS
Host must match exactlyk7m2x9p4q1.localtolink.com ≠ localhost
Path must match/auth/callback ≠ /auth/callback/ (trailing slash matters for some providers)
No wildcards (usually)Cannot register *.localtolink.com — add each subdomain individually

Practical tip: Register both localhost and tunnel URLs during development if your provider allows multiple redirect URIs. Switch between them depending on how you access the app.

Step-by-step OAuth flow through a tunnel

  1. User opens https://SUBDOMAIN.localtolink.com/login
  2. Your app redirects to the provider's authorization URL
  3. User authenticates with the provider
  4. Provider redirects to https://SUBDOMAIN.localtolink.com/auth/callback?code=...
  5. Your handler exchanges the code for tokens server-side
  6. User lands in your authenticated app

The callback request is a browser navigation — you may see the safety prompt on first visit (see below).

Browser interstitial warning

When the visitor interstitial policy is enabled, OAuth browser redirects may show LocalToLink's Opening a shared link page before your callback route runs. Continue once per subdomain, or use localtolink-skip-browser-warning in automated browser tests.

Server-side token exchange and provider webhook POSTs use non-browser clients and normally skip the interstitial. Only human navigations in a browser need the continue flow.

Provider-specific configuration

Google OAuth

  1. Open Google Cloud Console → APIs & Services → Credentials
  2. Edit your OAuth 2.0 Client ID
  3. Add under Authorized redirect URIs: https://SUBDOMAIN.localtolink.com/auth/callback
  4. Save and wait a few minutes for propagation

Google may also require the tunnel URL under Authorized JavaScript origins if your frontend makes direct API calls.

GitHub OAuth App

  1. GitHub → Settings → Developer settings → OAuth Apps
  2. Set Authorization callback URL to your tunnel URL
  3. Update when subdomain changes

For GitHub Apps (not OAuth Apps), configure the callback URL in the app settings similarly.

Auth0

  1. Auth0 Dashboard → Applications → Your App → Settings
  2. Add tunnel URL to Allowed Callback URLs
  3. Also add to Allowed Logout URLs and Allowed Web Origins if needed

Microsoft / Azure AD

Register the redirect URI under App registrations → Authentication → Redirect URIs. Microsoft requires exact HTTPS matches.

Combining OAuth with webhooks

If your app uses both OAuth and webhooks (common with Shopify, Slack apps), register the same tunnel subdomain for both:

SettingExample URL
Redirect URIhttps://SUBDOMAIN.localtolink.com/auth/callback
Webhook URLhttps://SUBDOMAIN.localtolink.com/webhooks/provider

See Test Shopify Webhooks in Development for a combined OAuth + webhook example.

Limitations on the free tier

LimitationImpact
Random subdomain per sessionUpdate provider settings when you restart the tunnel
2-hour session limitRestart tunnel and update redirect URI for longer sessions
No persistent URLNew session after reconnect grace = new subdomain
Custom subdomainsNot available on the free tier yet

The 10-minute reconnect grace keeps the same URL after brief network drops. After that window, you get a new subdomain.

Troubleshooting

ErrorCauseFix
redirect_uri_mismatchURI not registered or typoCopy exact URL from terminal; check trailing slash
OAuth works on localhost but not tunnelRegistered localhost onlyAdd tunnel URL to provider settings
Infinite redirect loopCallback handler misconfiguredVerify callback route exchanges code correctly
Interstitial blocks OAuthFirst visit to subdomainClick through warning page once
Token exchange failsUsing wrong client secretConfirm OAuth app credentials match environment

FAQ

Can I use localhost and tunnel URLs interchangeably?
Only if both are registered and you initiate the flow from the matching origin.

Do mobile OAuth flows work?
Yes. Scan the terminal QR code to open the tunnel URL on your phone. See Test Localhost on Your Phone.

What about PKCE?
PKCE works through the tunnel — it is a client-side flow modification, not affected by the tunnel layer.

For n8n OAuth specifically, see n8n OAuth and Webhooks on Localhost.

Try it now

Start a tunnel from your project directory — no account required.

$ npx --yes localtolink@latest

See the full CLI reference for flags and exit codes.

LocalToLink CLI 1.0.1 — verified 2026-09-20