Next.js Discord

Discord Forum

Login redirect callback issue

Unanswered
Hassib posted this in #help-forum
Open in Discord
I'm facing a weird issue when logging in, but it happens only when the app is deployed. It's working fine when I'm testing it on localhost.

I'm using AzureAD to log in and then making a request to get the exchange token for the backend, nothing is undefined. When trying to hit the login button, it gets the backend exchange token, and logs the user in and redirects user to the dashboard page which is this route "/". But whenever I deploy it, the login button works, but this happens:

- Assume the app URL is example.com, when user hits login, the app URL becomes example.com/?callbackUrl=https%3A%2F%2Fexample.com%2F and trying again doesn't make a difference.

The login route is /login
The dashboard route is /

I'm using next-auth.

Code is in the comments

6 Replies

import NextAuth from 'next-auth';
import AzureADProvider from 'next-auth/providers/azure-ad';
import axios from 'axios';
import { jwtDecode } from 'jwt-decode';
export const authOptions = {
  providers: [
    AzureADProvider({
      id: 'azure-ad',
      clientId: process.env.AZURE_AD_CLIENT_ID,
      clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
      tenantId: process.env.AZURE_AD_TENANT_ID,
      name: 'Login with Al Jazeera Media Network',
      providerLogoPath: `${process.env.NEXTAUTH_URL}/assets/ajsvgs/al_jazeera.svg`,
      style: {
        logo: `${process.env.NEXTAUTH_URL}/assets/ajsvgs/al_jazeera.svg`
      },
      profile(profile) {
        return {
          id: profile.oid || profile.sub,
          name: profile.name,
          email: profile.email
        };
      }
    })
  ],
callbacks: {
    async jwt({ token, account }) {
      // Only perform token exchange during initial sign-in
      if (account?.access_token) {
        try {
          const res = await axios.get(
            `${process.env.NEXT_PUBLIC_BACKEND_SERVER_BASE_URL}/sanad-token-exchange/`,
            {
              headers: {
                Authorization: `Bearer ${account.access_token}`,
                secretkey: process.env.SANAD_SECRET_KEY || ''
              }
            }
          );

          if (res.data?.success) {
            token.backendToken = res.data.access_token;
            token.refresh_token = res.data.refresh_token;

            // Decode the backend token to get expiration info
            const decoded = jwtDecode(token.backendToken);
            token.expt = decoded.exp;
            token.ist = decoded.iat;
          }
        } catch (error) {
          console.error('Token exchange failed:', error.message);
          // Don't fail the authentication, just log the error
        }
      }
      return token;
    },
    async session({ session, token }) {
      // Send backend token and related info to the client
      session.backendToken = token.backendToken;
      session.refresh_token = token.refresh_token;
      session.expt = token.expt;
      session.ist = token.ist;
      return session;
    }
  },
pages: {
    signIn: '/login',
    signOut: '/'
  },
  secret: process.env.NEXTAUTH_SECRET,
  debug: false
};

export default NextAuth(authOptions);
Middleware

import { NextMiddleware, NextRequest, NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';

export const middleware: NextMiddleware = async (req: NextRequest) => {
  // Remove x-middleware-prefetch header to prevent VIP's infrastructure
  // from caching empty JSON responses on prefetched data for SSR pages. See the following URLs for more info:
  // https://github.com/vercel/next.js/discussions/45997
  // https://github.com/vercel/next.js/pull/45772
  // https://github.com/vercel/next.js/blob/v13.1.1/packages/next/server/base-server.ts#L1069
  const headers = new Headers(req.headers);
  headers.delete('x-middleware-prefetch');

  // Required health check endpoint on VIP. Do not remove.
  if (req.nextUrl.pathname === '/cache-healthcheck') {
    return NextResponse.json({ message: 'ok' }, { status: 200 });
  }

  // Authentication checks
  const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
  const { pathname } = req.nextUrl;

  // Public routes that don't require authentication
  const publicRoutes = ['/login', '/api/auth'];
  const isPublicRoute = publicRoutes.some((route) =>
    pathname.startsWith(route)
  );

  // If user is not authenticated and trying to access protected route
  if (!token && !isPublicRoute && pathname !== '/') {
    const loginUrl = new URL('/login', req.url);
    return NextResponse.redirect(loginUrl);
  }

  // If user is authenticated and trying to access login page, redirect to home
  if (token && pathname === '/login') {
    const homeUrl = new URL('/', req.url);
    return NextResponse.redirect(homeUrl);
  }

  // Continue as normal through the Next.js lifecycle.
  return NextResponse.next({
    request: {
      headers
    }
  });
};
export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api/auth (auth API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * - public folder
     */
    '/((?!api/auth|_next/static|_next/image|favicon.ico|public).*)'
  ]
};