Clerk: redirect loop after authentication [2026 fix]

Clerk redirect loops occur when post-auth callback URLs don't match configured domains. Fix: verify CLERK_REDIRECT_URL and allowed origins match exactly.

Clerk: Redirect Loop After Authentication [2am Fix]

TL;DR

Cause: Your afterSignInUrl or afterSignUpUrl callback points to a domain/port that isn't registered in Clerk Dashboard's Allowed Redirect URIs.

Fix: Add your exact callback URL (including protocol and port) to Clerk Dashboard → Applications → Settings → Allowed Redirect URIs, then clear browser cache.

---

Real Console Error Messages

``` 1. "Error: Redirect uri not whitelisted" at ClerkClient.validateRedirectUri

2. "[Clerk] The redirect_uri '${uri}' is not in your instance's list of allowed redirect URIs." Error Code: CLERK_REDIRECT_NOT_ALLOWED

3. "POST /auth/callback 401 Unauthorized" response: {error: "invalid_grant", error_description: "Redirect URI mismatch"}

4. "GET /auth/verify?code=xyz - redirect loop detected" Loop count: 5, stopped at /dashboard (infinite redirect)

5. "CORS error: Origin 'http://localhost:3001' not allowed by access-control-allow-origin" (Often masked as redirect loop in browser) ```

---

Broken Code → Exact Fix

❌ BROKEN: Mismatched Callback URLs

```javascript // middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isPublicRoute = createRouteMatcher(['/']);

export default clerkMiddleware((auth, req) => { if (!isPublicRoute(req)) auth().protect(); });

export const config = { matcher: ['/((?!.*\..*|_next).*)'], };

// pages/dashboard.tsx import { useUser } from '@clerk/nextjs';

export default function Dashboard() { const { user } = useUser(); // Component redirects here after signin, but URL isn't whitelisted return <div>Welcome, {user?.firstName}</div>; }

// .env.local CLERK_PUBLISHABLE_KEY=pk_test_xxx CLERK_SECRET_KEY=sk_test_xxx // Missing: CLERK_REDIRECT_URL or callback configuration ```

Problem: When user signs in, Clerk tries redirecting to http://localhost:3000/dashboard but that exact URL isn't in Clerk Dashboard's whitelist. Browser loops between auth and callback indefinitely.

✅ FIXED: Explicit Redirect Configuration + Whitelist

```javascript // middleware.ts (unchanged, but now works) import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isPublicRoute = createRouteMatcher(['/']);

export default clerkMiddleware((auth, req) => { if (!isPublicRoute(req)) auth().protect(); });

export const config = { matcher: ['/((?!.*\..*|_next).*)'], };

// pages/dashboard.tsx (explicit redirect) import { useUser } from '@clerk/nextjs'; import { useRouter } from 'next/router'; import { useEffect } from 'react';

export default function Dashboard() { const { user, isLoaded } = useUser(); const router = useRouter();

useEffect(() => { if (isLoaded && !user) { // Use redirectUrl from ClerkProvider instead of relying on middleware router.push('/auth/sign-in'); } }, [isLoaded, user, router]);

if (!isLoaded) return <div>Loading...</div>;

return <div>Welcome, {user?.firstName}</div>; }

// _app.tsx (SET THIS) import { ClerkProvider } from '@clerk/nextjs';

function MyApp({ Component, pageProps }) { return ( <ClerkProvider> <Component {...pageProps} /> </ClerkProvider> ); }

export default MyApp;

// .env.local (ADD THIS) CLERK_PUBLISHABLE_KEY=pk_test_xxx CLERK_SECRET_KEY=sk_test_xxx CLERK_REDIRECT_URL=http://localhost:3000 ```

In Clerk Dashboard: 1. Go to Applications → Settings → Allowed Redirect URIs 2. Add exact entries: - http://localhost:3000 (development) - http://localhost:3000/dashboard (optional, but recommended) - https://yourdomain.com (production) - https://yourdomain.com/dashboard (if using explicit paths) 3. Click Save 4. Clear browser cache: Cmd+Shift+Delete or Ctrl+Shift+Delete 5. Restart dev server: stop and npm run dev

---

Still Broken? Check These Too

1. [Cookie Domain Mismatch](/?guide=clerk-cookies) — If testing across localhost:3000 and 127.0.0.1:3000, Clerk cookies won't transfer. Standardize on one.

2. Development vs Production Keys — Swapped CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY? Verify you're using pk_test_ (publishable) and sk_test_ (secret) correctly in .env.local.

3. [Clerk Org ID Configuration](/?guide=clerk-orgs) — If using Organizations feature, missing orgId in useOrganization() hook can cause silent redirects.

---

Version-Specific Notes

  • @clerk/nextjs >= 4.27.0: Redirect loop behavior changed; explicit ClerkProvider configuration now required. Earlier versions used implicit defaults.
  • Next.js 13+ App Router: Use clerkMiddleware() in middleware.ts at project root. Pages Router uses <ClerkProvider> wrapper.
  • Uncertain: Whether your Clerk instance uses legacy OAuth2 flow (pre-2024) vs current OIDC. Check Clerk Dashboard → Advanced if loops persist after trying this fix.
  • ---

    Official Resources

  • [Clerk Redirect URIs Documentation](https://clerk.com/docs/deployments/configure-allowed-redirect-uris)
  • [Clerk Next.js Integration Guide](https://clerk.com/docs/quickstarts/nextjs)
  • ---

    Found a different variation? Drop it in the comments—redirect loops can hide several root causes, and we're tracking all of them for the next update.

    🔥 0d
    LIVE
    ↓ PlanetScale rage spiking •⚠ Vercel pricing complaints •↑ Railway gaining fast •↑ Supabase happiness rising •↑ Resend loved by devs •↓ PlanetScale rage spiking •⚠ Vercel pricing complaints •↑ Railway gaining fast •↑ Supabase happiness rising
    DEVELOPER PAIN RADAR // Loading...

    Developers complain.
    Opportunities appear.

    We track what developers are struggling with today — and what opportunities that creates.

    —
    guides today
    —
    avg happiness
    🔥 Pain
    📖 Guides
    🔭 Explore
    👤 Mine
    🔥 Pain Radar — rage scores today
    ↗ share
    💡 Opportunity Feed — pain = market gap
    📈 Tool Momentum
    all scores →
    📖 Latest Guide
    all guides →
    📖 All Guides
    📊 Tool Scores
    + Submit
    📰 Hacker News
    ➕ Submit a Tool
    ← back