Why login didn't redirect & pages were accessible without auth

Short: middleware was not executed (no logs), session callback wasn't called, signin POST set a cookie but browser navigation didn't redirect — root cause: middleware placement and edge-compatibility/imports. This doc explains investigation, root cause, fix, and verification.

Impact

Pages accessible without auth

Observed

Signin silent success (blank), errors on wrong creds

Root Cause

Middleware not loaded / edge import issue

Status

Resolved — middleware moved to src and made edge-safe

Problem → Solution → Impact

Problem

  • Login appeared successful but redirects didn’t happen.
  • Protected pages were reachable without authentication.
  • Middleware and session callback were not executing at all.

Solution

  • Traced absence of middleware logs; confirmed module not loaded.
  • Moved middleware to the expected path and made imports edge-safe.
  • Re-tested with logging to confirm execution and redirect behaviour.

Impact

  • Auth redirect works; protected pages are gated again.
  • Session callbacks and middleware run reliably.
  • Clear RCA for future auth/middleware changes.
Route protection
Before: bypassed After: enforced
Redirect flow
Before: silent failure After: consistent redirect
Debug visibility
Before: no middleware logs After: instrumented & verifiable
Reproduce & log

Confirm missing middleware/session callbacks with targeted logging.

Fix execution

Move middleware to /src, adjust imports for edge compatibility.

Verify redirects

Retest login and gated pages with logs to confirm correct flow.

Impact spotlight
  • Login UX works end-to-end; no silent failures.
  • Protected routes stay protected.
  • Actionable RCA for future onboarding or audits.
Overview

Problem statement

After successful credential authentication the app did not redirect the user back to the original page. The sign-in flow produced a session cookie (Auth.js / NextAuth), JWT callback logged, but the session callback did not run and middleware outputs were absent. Pages that should be protected were accessible without authentication.

Short diagnosis:
Middleware was not executing because Next.js did not load the middleware module from the expected location / or the middleware imported node-only server modules causing it to be ignored by the edge bundler. Moving middleware to the expected path and making it edge-safe resolved the issue.
Symptoms

What we saw

  • No console.log from middleware.ts (no startup output).
  • Pages protected by middleware remained accessible without authorization.
  • Correct credentials: signin POST returned set-cookie with an AuthJS session id, but UI showed nothing (no redirect).
  • Wrong credentials: explicit credentials error displayed (expected).
  • JWT callback logged (token created), but session callback never logged.
Investigation

What we checked

  • Server logs on dev start — middleware log absent.
  • Network: signin /api/auth/callback/credentials returned set-cookie.
  • Fetched /api/auth/session manually — session callback only executed when session was fetched (expected).
  • Examined file locations: middleware was located in project root while app used src/ layout; moving it to src/middleware.ts fixed detection.
  • Inspected imports: middleware file originally re-exported auth from server auth file which imported Prisma — that makes middleware not edge-safe.
Root cause

Detailed root cause

Two related issues combined to cause the behavior:

  1. Middleware discovery rule / path mismatch — Next.js only auto-detects middleware when it exists at one of the expected locations (/middleware.ts at project root or /src/middleware.ts). If your project uses src/ sources but middleware sits at the root (or vice-versa) Next won't load it.
  2. Edge runtime compatibility / import chain — middleware runs in an edge-like runtime and must be edge-safe. Re-exporting the server auth module (which imported Prisma / PrismaAdapter) forced Node-only modules into the middleware bundle. The edge bundler either failed or skipped the middleware, resulting in no middleware execution and no logs.
Solution

Fixes applied

  • Move middleware to the correct location (src/middleware.ts) to satisfy Next's discovery when the app uses src/.
  • Make middleware edge-safe: avoid importing server-only modules (Prisma), and use getToken from next-auth/jwt or withAuth from next-auth/middleware. This prevents Node-only code entering the edge bundle.
  • Keep server-side auth separate: keep auth.ts (NextAuth & PrismaAdapter) as a server-only module used by the API route /api/auth/[...nextauth].
  • Ensure callback behavior: implement a redirect callback in NextAuth to return the homepage when callbackUrl is missing (fallback to baseUrl).

Edge-safe middleware (recommended)

// src/middleware.ts (edge-safe)
import { NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";

export async function middleware(req) {
  const pathname = req.nextUrl.pathname;
  if (pathname.startsWith('/api/auth') || pathname.startsWith('/_next') || pathname.startsWith('/favicon.ico')) return NextResponse.next();
  const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
  if (!token) {
    const signInUrl = new URL('/api/auth/signin', req.url);
    signInUrl.searchParams.set('callbackUrl', req.url);
    return NextResponse.redirect(signInUrl);
  }
  return NextResponse.next();
}

export const config = { matcher: ["/((?!api/auth|_next|public|favicon.ico|.*\\.(?:png|jpg|jpeg|svg|gif|ico|css|js|map)).*)"] };

Redirect fallback in NextAuth

// inside auth.ts NextAuth options
callbacks: {
  async redirect({ url, baseUrl }) {
    if (!url) return baseUrl;
    if (url.startsWith('/')) return baseUrl + url;
    try {
      const u = new URL(url);
      if (u.origin === baseUrl) return url;
    } catch(e) {}
    return baseUrl;
  }
}
Verification

How we verified the fix

  • Restart dev server and confirm middleware console.log appears on startup.
  • Visit protected page → middleware redirects to /api/auth/signin?callbackUrl= with the correct callback URL.
  • Sign in with correct credentials — POST returns set-cookie and browser follows 302 back to callback URL.
  • GET to callback URL includes the session cookie; the protected page is accessible and session callback logs when the session endpoint or page requires the session.
Timeline

Sequence of events

Middleware located at project root, app uses src/ layout — Next didn't load middleware.
Confirmed JWT logs, set-cookie, absent middleware logs, and missing session callback logs.
Moved middleware to src/middleware.ts and made it edge-safe (used getToken), restarted server — middleware executed and redirects worked.
Protected pages enforced, signin redirect returned to original page, session flows logged as expected.
Action items

Immediate & follow-up

  • ✅ Move middleware to the expected path (src/middleware.ts when using src/).
  • ✅ Replace any middleware imports that pull server-only modules; use getToken or withAuth.
  • ✅ Ensure NEXTAUTH_URL and NEXTAUTH_SECRET are set in .env.local, and enable NEXTAUTH_DEBUG=true while debugging.
  • 🔁 Add an automated test that verifies unauthenticated requests are redirected to the signin page with callbackUrl.
  • 🔒 Audit middleware imports across the repo to catch accidental server-only imports in edge code.
Lessons learned

Key takeaways

  • Next.js middleware has strict discovery rules — place it exactly where Next expects it (root or src/).
  • Middleware runs in an edge-like runtime — avoid importing Node-only modules (Prisma, filesystem, native libs) into it.
  • When using built-in NextAuth flows, prefer normal form POST / server redirect flows for automatic navigation, or handle redirect:false responses explicitly in client code.
  • Use targeted console logs and NEXTAUTH_DEBUG to quickly identify which callbacks run (jwt vs session vs redirect).