The middleware failed to detect the user session following authentication, resulting in a redirection loop

I am currently working on a project using Next.js with Supabase authentication. I have encountered an issue where the user session is not being detected in the middleware after a successful authentication callback. This results in the middleware continuously redirecting to the sign-in page, creating a redirect loop.

Here is the relevant code:

/api/auth/callback/route.ts :

import { NextResponse, NextRequest } from "next/server";
import { createClient } from "@/utils/supabase/server";
import config from "@/config";

export const dynamic = "force-dynamic";

// This route is called after a successful login. It exchanges the code for a session and redirects to the callback URL (see config.js).
export async function GET(req: NextRequest) {
  const requestUrl = new URL(req.url);
  const code = requestUrl.searchParams.get("code");
  const next = requestUrl.searchParams.get("next");
  
  if (code) {
    const supabase = createClient();
    const result = await supabase.auth.exchangeCodeForSession(code);

    if (result.error) {
      console.error("Error exchanging code for session:", result.error);
      return NextResponse.redirect(requestUrl.origin + '/auth-error');
    }
  }

  return NextResponse.redirect(requestUrl.origin);
}

middelware.ts:

/* middleware.ts */

import { type NextRequest } from 'next/server';
import { updateSession } from '@/utils/supabase/middleware';

export async function middleware(request: NextRequest) {
  return await updateSession(request);
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'
  ]
};

/* utils/supabase/middleware.ts */

import { createServerClient } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";

export async function updateSession(request: NextRequest) {
    let supabaseResponse = NextResponse.next({
        request,
    });

    const supabase = createServerClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL!,
        process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
        {
            cookies: {
                getAll() {
                    return request.cookies.getAll();
                },
                setAll(cookiesToSet) {
                    cookiesToSet.forEach(({ name, value, options }) =>
                        request.cookies.set(name, value)
                    );
                    supabaseResponse = NextResponse.next({
                        request,
                    });
                    cookiesToSet.forEach(({ name, value, options }) =>
                        supabaseResponse.cookies.set(name, value, options)
                    );
                },
            },
        }
    );

    const {
        data: { user },
    } = await supabase.auth.getUser();

    if (
        !user &&
        !request.nextUrl.pathname.startsWith("/signin") &&
        !request.nextUrl.pathname.startsWith("/api")
    ) {
        const url = request.nextUrl.clone();
        
        url.pathname = "/signin";
        return NextResponse.redirect(url);
    }

    return supabaseResponse;
}

The problem:

  1. The /api/auth/callback route successfully calls exchangeCodeForSession.
  2. However, in the subsequent middleware, getUser() returns null.
  3. This leads to the middleware redirecting to the signin page due to the null user.

Logs:

GET /api/auth/callback?code=97738d2a-3b9f-4c2e-a1aa-d9c667478e29 307 in 29ms
user in middleware: null

What I've attempted: Logging the result of exchangeCodeForSession in the callback route shows a successful session creation.

Checked the middleware multiple times, but it consistently shows the user as null.

Questions:

Why is the user session not being detected in the middleware after a successful callback?

Is there a timing issue causing the middleware to run before the session is fully established?

How can I ensure that the session is properly set and detectable in the middleware after the callback?

What is the best approach to prevent the redirect loop while still securing routes requiring authentication?

Environment:

"@supabase/ssr": "^0.4.0",
"@supabase/supabase-js": "^2.38.3",
 "next": "^14.0.0"

Any insights or suggestions would be highly appreciated. Thank you!

Answer №1

By changing the Site URL to http://localhost:3000 instead of , I was able to resolve the issue.

Answer №2

Addressing Timing Concerns

  1. One potential issue could be that the middleware is running before the session has been fully established. To troubleshoot this, try adding a slight delay in your middleware to see if it helps resolve the problem. While not a permanent fix, this can help pinpoint any timing issues.

Ensuring Proper Cookie Management

  1. Confirm that the cookies are being set correctly after the session is initiated. Double-check in your callback function that the session cookies are properly set. You may need to manually set the cookies in the response from the /api/auth/callback route.

Below is an example of how to set the session cookies:

if (result.data) {
  // Set session cookies
  const { access_token, refresh_token } = result.data.session;
  const options = { path: '/', httpOnly: true, secure: process.env.NODE_ENV === 'production' };

  NextResponse.setCookie('supabase.auth.token', access_token, options);
  NextResponse.setCookie('supabase.auth.refresh_token', refresh_token, options);
}

Validation of Middleware Logic

In your middleware code, ensure that the cookies are correctly passed back. Modify the getAll method within the middleware to verify proper reading of cookies:

cookies: {
  getAll() {
    return Object.fromEntries(request.cookies.getAll().map(cookie => [cookie.name, cookie.value]));
  },
  setAll(cookiesToSet) {
    // Same as previous logic
  },
},

Verifying Execution Order of Middleware

Check that your middleware is executing in the intended sequence. Consider logging out the order of operations to confirm the authentication flow is correct.

- Avoiding Redirect Cascades

To prevent continuous redirect loops, include a condition in your middleware that checks for a specific query parameter or session state indicating recent user login activity.

For instance:

if (request.nextUrl.pathname === '/api/auth/callback') {
  // Bypass user check for this request
  return NextResponse.next();
}

Important Reminders

Double-check accurate cookie setting during authentication callbacks.

Validate cookie handling within the middleware layer.

Add log tracing to monitor flow progress.

Consider implementing mechanisms to skip certain checks on particular requests, such as the callback endpoint.

Answer №3

import { NextResponse, NextRequest } from "next/server";
import { createClient } from "@/utils/supabase/server";
import config from "@/config";

export const dynamic = "force-dynamic";

// Upon successful login, this route is triggered to exchange the code for a session and redirect to the callback URL specified in config.js.
export async function GET(req: NextRequest) {
  const requestUrl = new URL(req.url);
  const code = requestUrl.searchParams.get("code");
  const next = requestUrl.searchParams.get("next");
  console.log("next in auth callback:", next);
  if (code) {
    const supabase = createClient();
    const result = await supabase.auth.exchangeCodeForSession(code);
    
    console.log("exchangeCodeForSession result:", JSON.stringify(result, null, 2));

    if (result.error) {
      console.error("Error exchanging code for session:", result.error);
      return NextResponse.redirect(requestUrl.origin + '/auth-error');
    }

    // Additional properties like result.data can be accessed if necessary
  }

  // Redirect URL after signing in process completion
  return NextResponse.redirect(requestUrl.origin);
}
 

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When attempting to create a JavaScript class within a NextJS library that utilizes server-side code, an error is encountered on the frontend despite not

Currently, I am in the process of developing a NextJS project and working on constructing a library that can be utilized in future projects. As part of this library, I have created a JavaScript class detailed below: export class EmailManager { private ...

Error thrown due to uncaught type mismatch in jQuery AJAX request

I am currently working on transferring Rails-generated json data into Google Maps markers on a map. However, I am still in the learning process of jQuery/JavaScript and trying to grasp the concepts. Unfortunately, I encountered a confusing error message i ...

Running cy.task after all test suites can be done by adding the task in a

I need some guidance on running cy.task after executing all test suites. I have a file generated at the start of the tests that I would like to remove once they are completed. Regardless of whether any tests passed or failed, I want to trigger cy.task im ...

Verify whether the message received contains a specific text within the property

Currently, I am facing a challenge with displaying a specific div in my component template only if any incoming messages contain the TYPE_OTHER property. With numerous variations of the TYPE_OTHER identifier, I am pondering on creating a condition that can ...

Instructions on how to automatically play multiple video tags on one HTML page while also opening a modal

I'm working on an HTML page that needs to display two different videos in separate modals. My goal is to have each video start playing automatically when the play-video icon is clicked and the modal opens. I attempted this using the code snippet (vid ...

Using ajax requests to target numerous div elements

I am working with two pages in jQuery Mobile. Each page contains a div called content, where I load external pages using AJAX. Interestingly, the call works perfectly for "page1", but it doesn't seem to be working for "page2", even though both pages h ...

How can I add data to a relational table that includes a foreign key reference?

There are two tables that are related with a one to many relationship: envelopes: CREATE TABLE envelopes ( id integer DEFAULT nextval('envelope_id_seq'::regclass) PRIMARY KEY, title text NOT NULL, budget integer NOT NULL ); transact ...

Is there a way to store MongoDB collections in an array when integrating Mongoose with ExpressJS?

I am looking to extract collections from a MongoDB database and store them in an array, essentially creating an array of JavaScript objects. ...

Tips for accessing various JSON objects from a JSON document

My task involves extracting specific information from a JSON file using AJAX and jQuery. The structure of my JSON data is as follows: "Footwear": { "Adidas": [ { "id" : 0, &q ...

Incorporate a line break between the day and month on your JQuery datepicker

Is it possible to insert a line break or some other element between the day and month in the input field when using JQuery datepicker? Here is the current code I have: jQuery('#checkin').datepicker({ showAnim: "drop", dateFormat ...

Tips for creating a login/registration system in a single page application

I've been working on a single-page application with ngRoute for navigation between tabs. Now, I need to incorporate a login and registration feature. After that, users should be able to access all the tabs. I'm unsure about the following: 1) Sho ...

Utilizing classes as types in TypeScript

Why is it possible to use a class as a type in TypeScript, like word: Word in the provided code snippet? class Dict { private words: Words = {}; // I am curious about this specific line add(word: Word) { if (!this.words[word.term]) { this.wor ...

JavaScript validation controls do not function properly when enabled on the client side

Following the requirements, I have disabled all validation controls on the page during the PageLoad event on the server side. When the submit button is clicked, I want to activate the validations and check if the page is valid for submission. If not, then ...

Assign a class to the element only when the second div also has a class

I am trying to create a functionality where I have a dropdown element (Li element) that receives an Active class when its parent div (button) is clicked. When the dropdown element has this class, I want to assign the same class to another div. If the dropd ...

Creating a pure function in JavaScript and React: A step-by-step guide

I am dealing with a dataset structured as follows: const arr_obj = [ { id: '1', children: [], type: 'TYPE1', }, { id: '2', children: [ { id: &apos ...

Changing a transparent div with overlays into a visual representation of its underlying background

I am curious to know if it is feasible to carry out the operation mentioned, given that JavaScript doesn't currently have access to the contents of certain objects, such as a Flash video player. I have explored various screenshot plugins, but none of ...

Transforming a plain text field into an input field upon clicking a button or icon using Angular/Typescript

I am a beginner with Angular 6 and I'm trying to implement functionality where clicking a button or icon will change a text field into an input field. See the snippet of code and expected output below. Thank you in advance. <div> <mat-for ...

Directive for integrating Amcharts with Angular JS

I have created a custom directive specifically for displaying a single chart using Amcharts. angular.module("App").directive('myElem', function () { return { restrict: 'E', replace:true, temp ...

What is the best way to show a table with 100% width in the Chrome browser?

I am currently working on debugging and expanding an express application that showcases a dataset through a series of nested tables on a webpage. Initially, all the CSS resided within style tags in the head section of the HTML file, and the tables were dis ...

Determining the Area of a Polygon Based on its Slope or Angle

Is it possible to calculate the area of a polygon when it has an inclination or slope? I have both the angle/slope and the points of the polygon, and I need to determine the area. How can this be achieved? ...