Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HandleLogin Override Login Handler Not working In Next.js 14.1.4 App Router #1796

Open
6 tasks done
ymonye opened this issue Nov 10, 2024 · 1 comment
Open
6 tasks done

Comments

@ymonye
Copy link

ymonye commented Nov 10, 2024

Checklist

Description

Hi, my Auth0 app is on Next.js 14.1.4 App Router. I am looking to create a login / signup button that takes my users directly to a social login. In my case the Google / Gmail (google-oauth2) screen, while overriding the Auth0 login prompt, eliminating an extra step for Gmail users. I already have both (passwordless) email & Google authentication working 100% without any issues. Universal Login is configured, with the Identifier First login flow for the passwordless experience.

The official docs state this is explicitly possible, yet I cannot get it to work on App Router.
https://auth0.github.io/nextjs-auth0/types/handlers_login.HandleLogin.html

From the docs:

Example

Override the login handler

import { handleAuth, handleLogin } from '@auth0/nextjs-auth0';

export default handleAuth({
  login: async (req, res) => {
    try {
      await handleLogin(req, res, {
        authorizationParams: { connection: 'github' }
      });
    } catch (error) {
      console.error(error);
    }
  }
});

Can't upgrade Next.js from 14.1.4 to 14.2.* due to unrelated issues here which breaks my & other users' code: #1776. However this issue is on a different subject.

Reproduction

My app router route is defined under app/api/auth/[auth0]/route.js as:

import { handleAuth } from '@auth0/nextjs-auth0';

export const GET = handleAuth();

As of now, the login feature is wrapped around a button as the below:
<a href="/api/auth/login"><button>Login</button></a>

Attempt 1 out of 7

My first attempt was appending this url with ?connection=google-oauth2, which still takes me directly to the Auth0 login page instead of directly to Gmail.

Attempt 2 out of 7

This involves creating a new route:
api/auth/login-google

Within this route I have the HandleLogin export with the override explicitly stated here:
https://auth0.github.io/nextjs-auth0/types/handlers_login.HandleLogin.html

I am attempting to convert the login override example from Page Router to App Router, yet this returns HTTP ERROR 500. Perhaps this is where things are breaking & could easily be solved instead of progressing to attempts 3 - 7.

import { handleAuth, handleLogin } from '@auth0/nextjs-auth0';

export const dynamic = 'force-dynamic';

export const GET = handleAuth({
  login: handleLogin((req) => {
    return {
      authorizationParams: { connection: 'google-oauth2' }
    };
  }),
});

Attempt 3 out of 7

Remove handleAuth

import { handleLogin } from '@auth0/nextjs-auth0';

export const GET = handleLogin({
    authorizationParams: {
      connection: 'google-oauth2'
    }
});

Great, this works if I npm run dev my app, and I'm able to sign-in with Google & return to my app logged in. However this results in build errors:

src/app/api/auth/login-google/route.js
Type error: Route "src/app/api/auth/login-google/route.js" has an invalid "GET" export:
  Type "NextRequest | NextApiRequest" is not a valid type for the function's first argument.
    Expected "Request | NextRequest", got "NextRequest | NextApiRequest".
      Expected "Request | NextRequest", got "NextApiRequest".

Attempt 4 out of 7

I now modify my login-google/route.js with:

import { handleLogin } from '@auth0/nextjs-auth0';

export const dynamic = "force-dynamic";

export async function GET(request) {
  return handleLogin(request, {
    authorizationParams: {
      connection: 'google-oauth2'
    }
  });
}

This now directs me back to the Auth0 login page, instead of directly to Gmail. Not what I want.

Attempt 5 out of 7

The suggestion is to manually build the login with query parameters:

export const dynamic = "force-dynamic";

export async function GET() {
  const auth0BaseUrl = process.env.AUTH0_BASE_URL;
  const auth0Callback = process.env.AUTH0_CALLBACK
  const auth0IssuerBaseUrl = process.env.AUTH0_ISSUER_BASE_URL;
  const clientId = process.env.AUTH0_CLIENT_ID;
  const redirectUri = `${auth0BaseUrl}${auth0Callback}`;

  const loginUrl = `${auth0IssuerBaseUrl}/authorize?` +
    new URLSearchParams({
      client_id: clientId,
      redirect_uri: redirectUri,
      response_type: 'code',
      connection: 'google-oauth2'
    });

  return new Response(null, {
    status: 302,
    headers: { Location: loginUrl.toString() },
  });
}

I can build my project without errors, and clicking my button takes me directly to my Google login, however, after logging in I receive an HTTP ERROR 400 at my api/auth0/callback route.

Attempt 6 out of 7

At this point I'm maybe doing to much and perhaps I should've asked here before more attempts, but now the suggestion is to redo my api/auth/[auth0]/route.js to properly catch the callback route instead of returning HTTP ERROR 400.

I change from:

import { handleAuth } from '@auth0/nextjs-auth0';

export const GET = handleAuth();

To:

import { handleAuth, handleLogin, handleLogout, handleCallback, handleProfile } from '@auth0/nextjs-auth0';

export const dynamic = 'force-dynamic';

export async function GET(request) {
  const url = new URL(request.url);
  const pathname = url.pathname;

  try {
    if (pathname.endsWith('/login')) {
      return await handleLogin(request, {
        authorizationParams: {
          connection: 'google-oauth2',
          scope: 'openid profile email',
        },
      });

    } else if (pathname.endsWith('/logout')) {
      return await handleLogout(request);

    } else if (pathname.endsWith('/callback')) {
      return await handleCallback(request);

    } else if (pathname.endsWith('/me')) {
      return await handleProfile(request);

    } else {
      return handleAuth()(request);
    }
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: error.status || 400,
      headers: { 'Content-Type': 'application/json' },
    });
  }
}

Ok, I can build without issues and login, but then the callback route returns the error:

{"error":"Callback handler failed. CAUSE: Missing state cookie from login request (check login URL, callback URL and cookie config)."}

Attempt 7 out of 7

I then rewrite my app/api/auth/login-google/route.js endpoint with:

import { randomBytes } from 'crypto';

export const dynamic = "force-dynamic";

export async function GET() {
  const auth0BaseUrl = process.env.AUTH0_BASE_URL;
  const auth0Callback = process.env.AUTH0_CALLBACK;
  const auth0IssuerBaseUrl = process.env.AUTH0_ISSUER_BASE_URL;
  const clientId = process.env.AUTH0_CLIENT_ID;
  const redirectUri = `${auth0BaseUrl}${auth0Callback}`;

  const desiredLength = 64;

  const state = randomBytes(Math.ceil(desiredLength / 2)).toString('hex').slice(0, desiredLength);

  const loginUrl = `${auth0IssuerBaseUrl}/authorize?` +
    new URLSearchParams({
      client_id: clientId,
      redirect_uri: redirectUri,
      response_type: 'code',
      connection: 'google-oauth2',
      state: state,
    }).toString();

  return new Response(null, {
    status: 302,
    headers: {
      Location: loginUrl,
      'Set-Cookie': `auth_state=${state}; HttpOnly; Secure; Path=/; SameSite=Lax`,
    },
  });
}

Returns the error:

{"error":"Callback handler failed. CAUSE: state mismatch, expected ********************************************************, got: ********************************************************"}

These are 2 different state codes btw. This is where I give up, perhaps there's an Auth0 recommended way of generating the state within my login-google route, or maybe there's even simpler overall code.

Any help would be greatly appreciated, thanks!

Additional context

No response

nextjs-auth0 version

3.5.0

Next.js version

14.1.4

Node.js version

20.15.1

@ymonye
Copy link
Author

ymonye commented Nov 11, 2024

cc @guabu

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant