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

Unable to getAccessToken for some users on Vercel Deployment #1045

Closed
7 tasks done
jonahallibone opened this issue Feb 2, 2023 · 8 comments
Closed
7 tasks done

Unable to getAccessToken for some users on Vercel Deployment #1045

jonahallibone opened this issue Feb 2, 2023 · 8 comments
Labels
question Further information is requested

Comments

@jonahallibone
Copy link

jonahallibone commented Feb 2, 2023

Checklist

  • The issue can be reproduced in the sample app (or N/A).
  • I have looked into the README and have not found a suitable solution or answer.
  • I have looked into the examples and have not found a suitable solution or answer.
  • I have looked into the API documentation and have not found a suitable solution or answer.
  • I have searched the issues and have not found a suitable solution or answer.
  • I have searched the Auth0 Community forums and have not found a suitable solution or answer.
  • I agree to the terms within the Auth0 Code of Conduct.

Description

Currently we are using Next.js api routes to get an accessToken to interact with our api server.

const proxy = async (req: NextApiRequest, res: NextApiResponse) => {
  const { accessToken } = await auth0.getAccessToken(req, res);
  ....
}

For one user, we are unable to use getAccessToken() in production. On our local deployments the problem does not exist. Every other user can get an accessToken with getAccessToken() just fine. In fact, it seems that their session is created incorrectly, or is just removed entirely. withMiddlewareAuthRequired allows the app to navigate to the app, but upon arrival it fails to access /auth/api/me or any of our api routes which require authentication.

We are using this in conjunction with withMiddlewareAuthRequired() and deploying to Vercel.

The error we recieve in our server logs is the following:

[GET] /api/proxy/user/foundation
13:13:22:02
2023-02-02T18:13:22.061Z	734e89d0-d4ea-48d5-994e-0b118baa0059	ERROR	[AccessTokenError: The user does not have a valid session.] {
  code: 'ERR_MISSING_SESSION',
  cause: undefined,
  status: undefined
}
2023-02-02T18:13:22.062Z	734e89d0-d4ea-48d5-994e-0b118baa0059	ERROR	[AccessTokenError: The user does not have a valid session.] {
  code: 'ERR_MISSING_SESSION',
  cause: undefined,
  status: undefined
}
RequestId: 734e89d0-d4ea-48d5-994e-0b118baa0059 Error: Runtime exited with error: exit status 1
Runtime.ExitError

In order to deploy to Vercel we have the following code:

[...auth0].ts

import { AuthError } from "@auth0/nextjs-auth0";
import type { NextApiRequest, NextApiResponse } from "next";
import auth0 from "util/auth0";

const audience = process.env.AUTH0_AUDIENCE;
const scope =
  "openid profile email offline_access read:current_user update:current_user_metadata";

function getUrls(req: NextApiRequest) {
  const { host } = req.headers;
  const protocol = process.env.VERCEL_URL ? "https" : "http";
  const redirectUri = `${protocol}://${host}/api/auth/callback`;
  const returnTo = `${protocol}://${host}`;
  return {
    redirectUri,
    returnTo,
  };
}

export default auth0.handleAuth({
  async callback(req: NextApiRequest, res: NextApiResponse) {
    try {
      const { redirectUri } = getUrls(req);
      await auth0.handleCallback(req, res, {
        redirectUri,
        authorizationParams: {
          audience,
          scope,
        },
      });
    } catch (error) {
      if (error instanceof AuthError) {
        res.status(error.status || 500).end(error.message);
      }
    }
  },

  async login(req: NextApiRequest, res: NextApiResponse) {
    try {
      const { redirectUri, returnTo } = getUrls(req);

      await auth0.handleLogin(req, res, {
        authorizationParams: {
          prompt: "login",
          audience,
          scope,
          redirect_uri: redirectUri,
        },
        returnTo,
      });
    } catch (error) {
      if (error instanceof AuthError) {
        res.status(error.status || 400).end(error.message);
      }
    }
  },

  async logout(req: NextApiRequest, res: NextApiResponse) {
    const { returnTo } = getUrls(req);
    await auth0.handleLogout(req, res, {
      returnTo,
    });
  },
});

[...proxy].ts

import type { NextApiRequest, NextApiResponse } from "next";
import httpProxyMiddleware from "next-http-proxy-middleware";
import auth0 from "util/auth0";

export const config = {
  api: {
    externalResolver: true,
    bodyParser: false,
  },
};

const proxy = async (req: NextApiRequest, res: NextApiResponse) => {
  // For one user it never get's past this point and errors at `getAccessToken()`
  const { accessToken } = await auth0.getAccessToken(req, res);

  console.log({ accessToken });
  

  
  const resp = await httpProxyMiddleware(req, res, {
    changeOrigin: true,
    target:
      process.env.NODE_ENV === "development"
        ? `http://localhost:8080/api`
        : process.env.BACKEND_URL,
    headers: {
      Authorization: `Bearer ${accessToken}`,
      Cookie: "",
    },
    pathRewrite: [{ patternStr: `^/api/proxy`, replaceStr: "" }],
  });
  return resp;
};

export default proxy;

middleware.ts

import auth0 from "util/auth0-edge";

const middleware = auth0.withMiddlewareAuthRequired();

export const config = {
  matcher: ["/", "/nonprofit/:path*", "/foundation/:path*"],
};

export default middleware;

util/auth0.ts

import { initAuth0 } from "@auth0/nextjs-auth0";

const auth0 = initAuth0({
  baseURL: process.env.VERCEL_URL
    ? `https://${process.env.VERCEL_URL}`
    : process.env.AUTH0_BASE_URL,
});

export default auth0;

util/auth0-edge.ts

import { initAuth0 } from "@auth0/nextjs-auth0/edge";

const auth0 = initAuth0({
  baseURL: process.env.VERCEL_URL
    ? `https://${process.env.VERCEL_URL}`
    : process.env.AUTH0_BASE_URL,
});

export default auth0;

I'm not sure if this is a bug or not, but it's incredibly hard to understand why the deployed app would be having a problem whilst the local one is fine.

Thanks!

Reproduction

Using the above example in a standard app should work.

SDK version

2.2.1

Next.js version

13.1.5

Node.js version

18

@jonahallibone
Copy link
Author

Note: I noticed that some sessions come as appSession.01 and appSession.02 and specifically users with these sessions end up being unable to authenticate

@jonahallibone
Copy link
Author

jonahallibone commented Feb 2, 2023

Update:

By dropping withAuthMiddlewareAuthRequired and manually checking for a user session, I was able to use my application.

middleware.ts

import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import auth0 from "util/auth0-edge";

const middleware = async (req: NextRequest) => {
  const res = NextResponse.next();
  const user = await auth0.getSession(req, res);
  if (user) {
    return NextResponse.next();
  }
  const baseURL = process.env.VERCEL_URL
    ? `https://${process.env.VERCEL_URL}`
    : process.env.AUTH0_BASE_URL;
  return NextResponse.redirect(`${baseURL}/api/auth/login`);
};

export const config = {
  matcher: ["/", "/nonprofit/:path*", "/foundation/:path*", "/admin:path*"],
};

export default middleware;

@adamjmcgrath
Copy link
Contributor

Thanks for raising this @jonahallibone - I'll investigate this and get back to

@adamjmcgrath adamjmcgrath added the needs investigation This needs to be investigated further before proceeding label Feb 6, 2023
@adamjmcgrath
Copy link
Contributor

Hi @jonahallibone - this looks like a bug in the vercel edge runtime, which will shortly be fixed by vercel/edge-runtime#255

I'll keep this open until that is released and hopefully you can confirm it's working

@adamjmcgrath adamjmcgrath added question Further information is requested and removed needs investigation This needs to be investigated further before proceeding labels Feb 7, 2023
@jonahallibone
Copy link
Author

@adamjmcgrath Great!

@adamjmcgrath
Copy link
Contributor

Gonna close this in favour of vercel/next.js#38302 - when that's shipped you should be able to update the next version to fix

@EiffelFly
Copy link

EiffelFly commented May 24, 2023

@adamjmcgrath We are facing similar issue and it's too random for us to deal with it

This is our version

  • nextjs 13.4.2
  • @auth0/nextjs-auth0 2.6.0

This is our configuration

import {
  AuthError,
  handleAuth,
  handleCallback,
  handleLogin,
  handleLogout,
} from "@auth0/nextjs-auth0";
import { NextApiRequest } from "next";

const audience = process.env.AUTH0_OAUTH_AUDIENCE;
const scope = process.env.AUTH0_OAUTH_SCOPE;

function getUrls(req: NextApiRequest) {
  const { host } = req.headers;
  const protocol = process.env.VERCEL_URL ? "https" : "http";
  const redirectUri = `${protocol}://${host}/api/auth/callback`;
  const returnTo = `${protocol}://${host}`;
  return {
    redirectUri,
    returnTo,
  };
}

export default handleAuth({
  async login(req, res) {
    try {
      const { redirectUri, returnTo } = getUrls(req);

      await handleLogin(req, res, {
        authorizationParams: {
          prompt: "login",
          audience,
          scope,
          redirect_uri: redirectUri,
        },
        returnTo,
      });
    } catch (error) {
      if (error instanceof AuthError) {
        res.status(error.status || 400).end(error.message);
      }
    }
  },
  async callback(req, res) {
    try {
      const { redirectUri } = getUrls(req);

      await handleCallback(req, res, {
        redirectUri,
        authorizationParams: {
          audience,
          scope,
        },
      });
    } catch (error) {
      if (error instanceof AuthError) {
        res.status(error.status || 500).end(error.message);
      }
    }
  },
  async logout(req, res) {
    const { returnTo } = getUrls(req);
    await handleLogout(req, res, {
      returnTo,
    });
  },
});

We are using this api to get accessToken

import { getAccessToken, withApiAuthRequired } from "@auth0/nextjs-auth0";
import { NextApiHandler } from "next";

const handler: NextApiHandler = async (req, res) => {
  try {
    const { accessToken } = await getAccessToken(req, res);
    res.status(200).json({ accessToken });
  } catch (err) {
    console.error(err);
    res.redirect(307, "/api/auth/login");
  }
};

export default withApiAuthRequired(handler);

I think we are in the similar issue as @jonahallibone . Because the user can't login has two session.

We try to alter the get-access-token to make this work but there has no luck

import { getAccessToken, getSession } from "@auth0/nextjs-auth0";
import { NextApiHandler } from "next";

const handler: NextApiHandler = async (req, res) => {
  const user = await getSession(req, res);

  if (!user || !user.accessToken) { // <---- can not get the access token
    res.redirect(307, "/api/auth/login");
    return;
  }

  res.status(200).json({ accessToken: user?.accessToken });
};

export default handler;

Can you provide some workaround before Vercel fix their problem? This has already affected our users

@dkokotov
Copy link

dkokotov commented Jun 7, 2023

@adamjmcgrath @jonahallibone I think there Is still actually an issue with https://github.com/auth0/nextjs-auth0/blob/main/src/utils/middleware-cookies.ts#L11 - even after the NextJS issue referenced above has been resolved.

I ran into this when implementing the suggestion described in #912. My use case was wanting to have an api proxy that runs on edge (to avoid occasional delays due to cold starts of a regular nodejs api route).

The basic problem here is that browsers expect multiple set-cookie headers, one for each cookie. the NextJS bug referenced above made it so instead a single comma-separated set-cookie header was returned. In the case where the auth0 cookie needs to be chunked this was causing a bad value for it to be stored at the browser, and subsequently retrieving session would no longer work.

With the NextJS bug fixed, it will correctly return multiple set-cookie headers from an edge route - but only if you call response.setCookie() or response.headers.set('Set-Cookie', ..) multiple times, for each cookie you want to be set in the response. I don't think it will work correctly if you call res.headers.set('Set-Cookie', ..) once with a comma-separated list of cookies, as is being done on https://github.com/auth0/nextjs-auth0/blob/main/src/utils/middleware-cookies.ts#L11.

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

No branches or pull requests

4 participants