typescriptnginxnext.jsamazon-cognitonext-auth

502 Bad Gateway in production with Nextjs next-auth with url '/api/auth/callback/cognito'


I'm trying to publish a nextjs app that uses 'next-auth' with aws Cognito.

When I run it locally, either using next dev OR next start it works completely fine.

When I run it on the production server (ubuntu, with nginx) it does not.

Exact Error: After accessing the Cognito built in sign in page the redirect url https://...../api/auth/callback/cognito?code=......&state=..... displays nginx's default 502 error page.

What I've checked:

And YES the Callback URL(s) setting for the app in AWS Cognito itself is set to the correct url (https:// ....... /api/auth/callback/cognito).

Details:

CODE:

middleware.ts

export { default } from "next-auth/middleware";

export const config = { matcher: ["/dashboard/:path*"] };

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  compiler: {
    styledComponents: true,
   
  },
};

module.exports = nextConfig;

pages/api/auth/[...nextauth].ts

import CognitoProvider from "next-auth/providers/cognito";
import NextAuth, { NextAuthOptions, Session } from "next-auth";
import {
  AuthFlowType,
  CognitoIdentityProviderClient,
  InitiateAuthCommand,
} from "@aws-sdk/client-cognito-identity-provider";
import { JWT } from "next-auth/jwt";

const COGNITO_AWS_REGION = process.env.COGNITO_AWS_REGION;
const COGNITO_POOL_ID = process.env.COGNITO_POOL_ID;
const COGNITO_CLIENT_ID = process.env.COGNITO_CLIENT_ID;
const COGNITO_CLIENT_SECRET = process.env.COGNITO_CLIENT_SECRET;
const NEXTAUTH_SECRET = process.env.NEXTAUTH_SECRET;
const NEXTAUTH_URL = process.env.NEXTAUTH_URL;
if (!COGNITO_AWS_REGION) throw new Error("REGION is not set");
if (!COGNITO_CLIENT_ID) throw new Error("COGNITO_CLIENT_ID is not set");
if (!COGNITO_POOL_ID) throw new Error("COGNITO_USER_POOL_ID is not set");
if (!COGNITO_CLIENT_SECRET) throw new Error("COGNITO_CLIENT_SECRET is not set");
if (!NEXTAUTH_SECRET) throw new Error("NEXTAUTH_SECRET is not set");
if (!NEXTAUTH_URL) throw new Error("NEXTAUTH_URL is not set");

const refreshCognitoAccessToken = async (token: JWT) => {
  const client = new CognitoIdentityProviderClient({
    region: COGNITO_AWS_REGION,
  });
  const command = new InitiateAuthCommand({
    AuthFlow: AuthFlowType.REFRESH_TOKEN_AUTH,
    ClientId: COGNITO_CLIENT_ID,
    AuthParameters: {
      REFRESH_TOKEN: token.refreshToken as string,
    },
  });
  const response = await client.send(command);
  return response.AuthenticationResult;
};

export const authOptions: NextAuthOptions = {
  secret: NEXTAUTH_SECRET,
  // @ts-expect-error -- this property is not documented properly
  site: NEXTAUTH_URL,
  providers: [
    CognitoProvider({
      clientId: COGNITO_CLIENT_ID!,
      issuer: `https://cognito-idp.${COGNITO_AWS_REGION}.amazonaws.com/${COGNITO_POOL_ID!}`,
      clientSecret: process.env.COGNITO_CLIENT_SECRET!,
      
    }),
  ],
  callbacks: {
    jwt: async ({ token, account, user }) => {
      // Initial sign in
      if (account && user) {
        return {
          // save token to session for authenticating to AWS
          // https://next-auth.js.org/configuration/callbacks#jwt-callback
          accessToken: account.access_token,
          accessTokenExpires: account.expires_at
            ? account.expires_at * 1000
            : 0,
          refreshToken: account.refresh_token,
          user,
        };
      }

      // Return previous token if the access token has not expired yet
      if (Date.now() < (token as unknown as Session).accessTokenExpires) {
        return token;
      }

      // Access token has expired, try to update it
      const refreshedTokens = await refreshCognitoAccessToken(token);
      return {
        ...token,
        accessToken: refreshedTokens?.AccessToken,
        accessTokenExpires: refreshedTokens?.ExpiresIn
          ? Date.now() + refreshedTokens?.ExpiresIn * 1000
          : 0,
        refreshToken: refreshedTokens?.RefreshToken ?? token.refreshToken, // Fall back to old refresh token
      };
    },

    session: async ({ session, token }) => {
      if (!session?.user || !token?.accessToken) {
        console.error("No accessToken found on token or session");
        return session;
      }
      session.user = token.user as Session["user"];
      session.accessToken = token.accessToken as string;
      session.error = token.error as string | undefined;

      return session;
    },
    redirect: async ({ url, baseUrl }) => {
      // allows any url
      if (url.startsWith("/")) return `${baseUrl}${url}`;
      return url;
    },
  },
};

export default NextAuth(authOptions);

Solution

  • Well I figured it out myself eventually, turns out I wasn't reading my nginx logs correctly, once I did it wasn't anything too hard... here is what I did:

    Most Important:

    The 502 error is most likely corresponding with a upstream sent too big header while reading response header from upstream error in the nginx error logs for that request. To solve this add this to your config, under /etc/nginx/nginx.conf in the http {... } section ...

    proxy_buffers 8 16k;
    proxy_buffer_size 32k;
    

    (found this solution here: https://stackoverflow.com/a/38758325/4205839)

    Additional Things To Try:

    The above should solve the 502 error, but you may still be getting errors with next-auth, here are a few other things to try which I discovered while trying to solve this problem...

    In AWS Cognito, try making, and then using, a new "App Client" in AWS Cognito WITHOUT a client secret.

    If you run it now you will get errors such as signin?error=OAuthCallback and client_secret_basic client authentication method requires a client_secret. So you will also need to update the config of cognito in pages/api/auth/[...nextauth].ts to be ...

    CognitoProvider({
    clientId: COGNITO_CLIENT_ID,
          issuer: `https://cognito-idp.${COGNITO_AWS_REGION}.amazonaws.com/${COGNITO_POOL_ID!}`,
          clientSecret: "someString",
          client: {
               token_endpoint_auth_method: "none",
          },
    })
    

    which I discovered from this discussion: https://github.com/nextauthjs/next-auth/issues/2524

    Note: If you receive something like a redirect_mismatch error from Cognito it means you haven't updated the urls correctly in your AWS Cognito client app settings, which is a frequent occurrence I've found when switching between local and live for debugging.