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
ClerkProvider configuration now required. Earlier versions used implicit defaults.clerkMiddleware() in middleware.ts at project root. Pages Router uses <ClerkProvider> wrapper.---
Official Resources
---
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.