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

unstable_getServerSession returns a string when there's a problem with the config AND custom error page is not set #5166

Closed
melanieseltzer opened this issue Aug 16, 2022 · 2 comments · Fixed by #5218
Labels
bug Something isn't working core Refers to `@auth/core`

Comments

@melanieseltzer
Copy link
Contributor

melanieseltzer commented Aug 16, 2022

Environment

System:
  OS: macOS 11.2.3
  CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
  Memory: 387.27 MB / 16.00 GB
  Shell: 5.8 - /bin/zsh
Binaries:
  Node: 16.16.0 - ~/.nvm/versions/node/v16.16.0/bin/node
  Yarn: 1.22.17 - /usr/local/bin/yarn
  npm: 8.11.0 - ~/.nvm/versions/node/v16.16.0/bin/npm
  Watchman: 4.9.0 - /usr/local/bin/watchman
Browsers:
  Chrome: 104.0.5112.79
  Firefox: 102.0.1
  Safari: 14.0.3
npmPackages:
  next: 12.2.5 => 12.2.5 
  next-auth: 4.10.3 => 4.10.3 
  react: 18.2.0 => 18.2.0 

Reproduction URL

https://github.com/melanieseltzer/next-auth-unstable_getServerSession-bug-repro

Describe the issue

Background

I am not using a custom error page:

// [...nextauth].ts

export const authOptions: NextAuthOptions = {
  // ...other irrelevant config
  pages: {
    signIn: '/auth/login'
    // NO custom error page, commented out
    // error: '/auth/error'
  }
};

My dashboard page:

// pages/dashboard.ts

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
  const session = await unstable_getServerSession(req, res, authOptions);

  if (!session) {
    return {
      redirect: {
        destination: '/api/auth/signin',
        permanent: false
      }
    };
  }

  return {
    props: { session }
  };
};

Bug

It's possible for unstable_getServerSession to return a string as the session in the scenario above, if these conditions are met:

  1. A custom error page must not be set in [...nextauth].ts
  2. assertConfig must return a ConfigError inside unstable_getServerSession aka an error is thrown

It's due to this check here right at the end before final return.

NextAuthHandler verifies everything is set up correctly with assertConfig under the hood. If assertConfig returns a ConfigError, then NextAuthHandler returns an object shaped differently depending if you've created a custom error page yourself or not.

If you've got a custom error page, that object is shaped like this:

{
  redirect: `${pages.error}?error=Configuration`,
}

Which fails this check so null is returned.

If you don't have a custom error page created, then the return from NextAuthHandler is shaped like this with a body key that is a string of html forming the default error page:

{
  cookies,
  status,
  headers: [{ key: "Content-Type", value: "text/html" }],
  body: `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>${css()}</style><title>${title}</title></head><body class="__next-auth-theme-${
    theme?.colorScheme ?? "auto"
  }"><div class="page">${renderToString(html)}</div></body></html>`,
}

This satisfies the if condition checking the existence of body.

So in this case body (meaning the string of error page html) is then returned as the session, and also asserted as the Session type, further concealing the issue.

In my case, I was confused because I was getting a NO_SECRET error in prod (as expected), and I was expecting my server redirect in getServerSideProps to kick in because a valid session is not expected in this case. But since I had not created a custom error page, the session still came through as a string, skipping the redirect logic entirely and rendering my /dashboard, causing an error Application error: a client-side exception has occurred 😅

// pages/dashboard.ts

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
  const session = await unstable_getServerSession(req, res, authOptions);

  console.log(typeof session);
  console.log(session);

  if (!session) {
    // would never get here in my case, since session exists as a string
  }
};

Result in terminal:

// string
// <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>:root{--border-width:1px;--border-radius:0.3rem;--color-error:#c94b4b;--color-info:#157efb;--color-info-text:#fff}.__next-auth-theme-auto,.__next-auth-theme-light{--color-background:#fff;--color-text:#000;--color-primary:#444;--color-control-border:#bbb;--color-button-active-background:#f9f9f9;--color-button-active-border:#aaa;--color-seperator:#ccc}.__next-auth-theme-dark{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}@media (prefers-color-scheme:dark){.__next-auth-theme-auto{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}}body{background-color:var(--color-background);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;margin:0;padding:0}h1{font-weight:400;margin-bottom:1.5rem;padding:0 1rem}h1,p{color:var(--color-text)}form{margin:0;padding:0}label{font-weight:500;margin-bottom:.25rem;text-align:left}input[type],label{color:var(--color-text);display:block}input[type]{background:var(--color-background);border:var(--border-width) solid var(--color-control-border);border-radius:var(--border-radius);box-shadow:inset 0 .1rem .2rem rgba(0,0,0,.2);box-sizing:border-box;font-size:1rem;padding:.5rem 1rem;width:100%}input[type]:focus{box-shadow:none}p{font-size:1.1rem;line-height:2rem;margin:0 0 1.5rem;padding:0 1rem}a.button{line-height:1rem;text-decoration:none}a.button,a.button:link,a.button:visited,button{background-color:var(--color-background);color:var(--color-primary)}a.button,button{border:var(--border-width) solid var(--color-control-border);border-radius:var(--border-radius);box-shadow:0 .15rem .3rem rgba(0,0,0,.15),inset 0 .1rem .2rem var(--color-background),inset 0 -.1rem .1rem rgba(0,0,0,.05);font-size:1rem;font-weight:500;margin:0 0 .75rem;padding:.75rem 1rem;position:relative;transition:all .1s ease-in-out}a.button:hover,button:hover{cursor:pointer}a.button:active,button:active{background-color:var(--color-button-active-background);border-color:var(--color-button-active-border);box-shadow:0 .15rem .3rem rgba(0,0,0,.15),inset 0 .1rem .2rem var(--color-background),inset 0 -.1rem .1rem rgba(0,0,0,.1);cursor:pointer}a.site{color:var(--color-primary);font-size:1rem;line-height:2rem;text-decoration:none}a.site:hover{text-decoration:underline}.page{display:grid;height:100%;margin:0;padding:0;place-items:center;position:absolute;width:100%}.page>div{padding:.5rem;text-align:center}.error a.button{display:inline-block;margin-top:.5rem;padding-left:2rem;padding-right:2rem}.error .message{margin-bottom:1.5rem}.signin a.button,.signin button,.signin input[type=text]{display:block;margin-left:auto;margin-right:auto}.signin hr{border:0;border-top:1px solid var(--color-seperator);display:block;margin:1.5em auto 0;overflow:visible}.signin hr:before{background:var(--color-background);color:#888;content:"or";padding:0 .4rem;position:relative;top:-.6rem}.signin .error{background:#f5f5f5;background:var(--color-info);border-radius:.3rem;font-weight:500}.signin .error p{color:var(--color-info-text);font-size:.9rem;line-height:1.2rem;padding:.5rem 1rem;text-align:left}.signin form,.signin>div{display:block}.signin form input[type],.signin>div input[type]{margin-bottom:.5rem}.signin form button,.signin>div button{width:100%}.signin form,.signin>div{max-width:300px}.signout .message{margin-bottom:1.5rem}.logo{display:inline-block;margin-top:100px;max-height:150px;max-width:300px}.card{border:1px solid var(--color-control-border);border-radius:5px;margin:50px auto;max-width:-webkit-max-content;max-width:-moz-max-content;max-width:max-content;padding:20px 50px}.card .header{color:var(--color-primary)}.section-header{color:var(--brand-color,var(--color-text))}</style><title>Error</title></head><body class="__next-auth-theme-auto"><div class="page"><div class="error"><div class="card"><h1>Server error</h1><div class="message"><div><p>There is a problem with the server configuration.</p><p>Check the server logs for more information.</p></div></div></div></div></div></body></html>

How to reproduce

These conditions must be true to experience it:

  1. A custom error page must not be set in [...nextauth].ts
  2. assertConfig must return a ConfigError inside unstable_getServerSession aka an error is thrown

Please go to my example to repro this (with specific instructions): https://github.com/melanieseltzer/next-auth-unstable_getServerSession-bug-repro#readme

Expected behavior

If there's a problem under the hood, I'd expect that unstable_getServerSession should return null regardless of whether you've created a custom error page or not.

This does mirror the behavior of getSession which always returns null if an error is caught in fetchData.

I can submit a PR if you agree with this expected behavior.

@melanieseltzer melanieseltzer added the triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. label Aug 16, 2022
@ThangHuuVu
Copy link
Member

Thank you for the detailed bug report!
I've tested your reproduction and can confirm. This shouldn't be expected. PR welcome 🙌 I'd be glad to take a look
While you're at it, please also add a test in getServerSession.test.ts

@ThangHuuVu ThangHuuVu added bug Something isn't working core Refers to `@auth/core` and removed triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. labels Aug 21, 2022
@melanieseltzer
Copy link
Contributor Author

Thanks @ThangHuuVu! PR created: #5218 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working core Refers to `@auth/core`
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants