next-auth icon indicating copy to clipboard operation
next-auth copied to clipboard

feat: customizable `authorize()` error

Open balazsorban44 opened this issue 1 year ago • 2 comments

This introduces a way to throw custom errors in the authorize() callback of the Credentials provider.

Any generic or sensitive server error will continue to return error=Configuration (full error is logged on the server), but for custom ones thrown from authorize() that extend CredentialsSignin, we will let the error propagate. Example:

import { CredentialsSignin } from "@auth/core/errors" // import is specific to your framework

class CustomError extends CredentialsSignin {
  code = "custom"
}

// ...
authorize() {
  // ...
  throw new CustomError()
}

Then, based on your framework, one of the following will happen:

client-side (fetch to the sign-in endpoint):

  • get redirected to the signin page as such /signin?error=CredentialsSignin&code=custom
  • if redirect: false was set on signIn, it returns the error and code properties.

server-side handled form actions:

  • CustomError is thrown that you would need to handle in a catch block.

Notes:

  • The name CredentialsSignin was chosen to be backwards compatible with NextAuth.js v4 https://github.com/nextauthjs/next-auth/blob/2072750c017c3d2a7f7374dd683055b79d331319/packages/next-auth/src/core/routes/callback.ts#L336
  • I also renamed AuthorizedCallbackError to AccessDenied for the same reason https://github.com/nextauthjs/next-auth/blob/2072750c017c3d2a7f7374dd683055b79d331319/packages/next-auth/src/core/routes/callback.ts#L99
  • Support for framework integrations like next-auth will be added in a separate PR

Related: #9099 (see https://github.com/nextauthjs/next-auth/issues/9099#issuecomment-1913754338). This PR won't close that issue yet, a next-auth PR will be opened after this gets merged

balazsorban44 avatar Feb 01 '24 03:02 balazsorban44

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
auth-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 2, 2024 4:21am
2 Ignored Deployments
Name Status Preview Comments Updated (UTC)
next-auth-docs ⬜️ Ignored (Inspect) Visit Preview Mar 2, 2024 4:21am
nextra-docs ⬜️ Ignored (Inspect) Visit Preview Mar 2, 2024 4:21am

vercel[bot] avatar Feb 01 '24 03:02 vercel[bot]

Codecov Report

Attention: Patch coverage is 86.99187% with 16 lines in your changes are missing coverage. Please review.

Project coverage is 41.68%. Comparing base (34b8995) to head (b361d08). Report is 53 commits behind head on main.

:exclamation: Current head b361d08 differs from pull request most recent head 1fa5116. Consider uploading reports for the commit 1fa5116 to get more accurate results

Files Patch % Lines
packages/core/src/index.ts 87.23% 6 Missing :warning:
packages/core/src/lib/actions/callback/index.ts 20.00% 4 Missing :warning:
packages/core/src/errors.ts 94.87% 2 Missing :warning:
packages/core/src/lib/utils/web.ts 60.00% 2 Missing :warning:
packages/core/src/types.ts 0.00% 2 Missing :warning:
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9871      +/-   ##
==========================================
- Coverage   42.56%   41.68%   -0.88%     
==========================================
  Files         173      158      -15     
  Lines       27752    25406    -2346     
  Branches     1194     1040     -154     
==========================================
- Hits        11813    10591    -1222     
+ Misses      15939    14815    -1124     

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

codecov[bot] avatar Feb 01 '24 03:02 codecov[bot]

Any updates/plans on when this will be merged?

AhmedBaset avatar Feb 20 '24 22:02 AhmedBaset

@ndom91 we will enable it again in my pr #100017 as I remember Balazs got some issues with the tests when he added the restart mock function

ThangHuuVu avatar Feb 22 '24 15:02 ThangHuuVu

Hi! Any updates/ETA on this? I am testing our migration to beta v5 and want to display custom error messages on signin flow from backend directly on the page without redirection.

WINOFFRG avatar Feb 25 '24 13:02 WINOFFRG

How do I use this in next.js? I don't install the @auth/core package and couldn't find it in any section of the CredentialsSignin library image image

MrOxMasTer avatar Mar 03 '24 07:03 MrOxMasTer

@MrOxMasTer do you mean how do you use this new feature? Or next-auth in general?

For next-auth in general, first of all, you do not need to install @auth/core any more. That's why you won't find it anywhere.

A straightforward setup description can be found here: https://authjs.dev/guides/upgrade-to-v5#configuration

You can also check out our Next.js example app here: https://github.com/nextauthjs/next-auth-example

ndom91 avatar Mar 03 '24 14:03 ndom91

@MrOxMasTer do you mean how do you use this new feature? Or next-auth in general?

How do I use this feature in next.js, which I am currently on pull request. I couldn't find this class anywhere. How do I use this new feature? Where do I import CredentialsSignin from. The latest version where this feature was added is installed image

MrOxMasTer avatar Mar 03 '24 14:03 MrOxMasTer

@MrOxMasTer you'd be surprised where people ask what :joy:

Anyway, sorry about that! So yeah the CredentialsSignin error class seems to only be exported from @auth/core/errors at the moment, which is not ideal as you're not supposed to install that package explicitly (it won't hurt if you do, but we're trying to avoid that additional complexity).

I've opened a PR reexporting it in the framework packages here: https://github.com/nextauthjs/next-auth/pull/10200

It also updates the docs example updated in this PR to show throwing the new custom error

ndom91 avatar Mar 03 '24 14:03 ndom91

I've opened a PR reexporting it in the framework packages here: #10200

It also updates the docs example updated in this PR to show throwing the new custom error

Thank you, I pray for you 🛐🛐🛐

MrOxMasTer avatar Mar 03 '24 14:03 MrOxMasTer

@MrOxMasTer the changes have been merged to main. You can now import CredentialsSignin from next-auth, for example.

ndom91 avatar Mar 04 '24 16:03 ndom91

Hi, I tried the CustomError solution suggested by @balazsorban44

export class CustomError extends CredentialsSignin {
  code = "custom";
}

However, my custom error message will always be appended with an additional string of " .Read more at https://errors.authjs.dev#credentialssignin", which I don't want to be sent to client.

Below is the screenshot of what my CustomError instance looks like in debugger (my custom error message is just "Login failed"): custom_error

Upon inspecting the source code, it seems the string is appended in the constructor of AuthError class, as shown in below link: https://github.com/nextauthjs/next-auth/blob/239dfcf71fb3267272e2d972f9551f6f8aea0105/packages/core/src/errors.ts#L75-L76

Currently I have to workaround it by adding a constructor to my CustomError class to overwrite the unwanted appended string:

export class CustomError extends CredentialsSignin {
  code = "custom";
  constructor(message?: any, errorOptions?: any) {
    super(message, errorOptions);
    this.message = message;
  }
}

Is there any elegant way to solve it? Thanks in advance.

ccyen8358 avatar Mar 06 '24 00:03 ccyen8358

I think you shouldn't depend on error.code not error.message

AhmedBaset avatar Mar 06 '24 04:03 AhmedBaset

@balazsorban44 this has broken the redirection for me. Instead of /<my-signin-page>?error=CredentialsSignin this now redirects to /api/auth/<my-signin-page>?error=CredentialsSignin&code=<code> which returns "Bad Request"

My config:

{
    pages: {
        signIn: '/<my-signin-page>',
        error: '/<my-signin-page>', // Error code passed in query string as ?error=
    },
    ...
    basePath: "/api/auth",
}

on build 13 of the beta this works as expected. I don't see any relevant changes in the regexp matching so am curious why this stopped working.

sjoukedv avatar Mar 06 '24 11:03 sjoukedv

@balazsorban44 this has broken the redirection for me. Instead of /<my-signin-page>?error=CredentialsSignin this now redirects to /api/auth/<my-signin-page>?error=CredentialsSignin&code=<code> which returns "Bad Request"

My config:

{
    pages: {
        signIn: '/<my-signin-page>',
        error: '/<my-signin-page>', // Error code passed in query string as ?error=
    },
    ...
    basePath: "/api/auth",
}

on build 13 of the beta this works as expected. I don't see any relevant changes in the regexp matching so am curious why this stopped working.

Because of basePath: "/api/auth"

AhmedBaset avatar Mar 06 '24 13:03 AhmedBaset

@balazsorban44 this has broken the redirection for me. Instead of /<my-signin-page>?error=CredentialsSignin this now redirects to /api/auth/<my-signin-page>?error=CredentialsSignin&code=<code> which returns "Bad Request" My config:

{
    pages: {
        signIn: '/<my-signin-page>',
        error: '/<my-signin-page>', // Error code passed in query string as ?error=
    },
    ...
    basePath: "/api/auth",
}

on build 13 of the beta this works as expected. I don't see any relevant changes in the regexp matching so am curious why this stopped working.

Because of basePath: "/api/auth"

Both with basePath not set and basePath: "/" it's giving the same behaviour.

sjoukedv avatar Mar 06 '24 14:03 sjoukedv

@ccyen8358 hmm interesting, so I can't htink of any other way to solve it currently. Other than adding an additional custom error class designed to be extended from for this use-case :thinking: I've put up a quick prototype PR of that here to hopefully get some discussion going there :pray:

@sjoukedv hmm interesting, do you have an AUTH_URL env var set as well? If so, what to? iirc there shouldn't be any changes related to that that shipped recently, as we're working on soemthing very similar (regarding basePath) here as we speak.

ndom91 avatar Mar 06 '24 15:03 ndom91

@ndom91

@sjoukedv hmm interesting, do you have an AUTH_URL env var set as well? If so, what to? iirc there shouldn't be any changes related to that that shipped recently, as we're working on soemthing very similar (regarding basePath) here as we speak.

// path next.config.js

/** @type {import("next").NextConfig} */
module.exports = {
    env: {
        AUTH_URL: `${process.env.VERCEL_URL || 'http://localhost:3000'}/api/auth`,
        NEXTAUTH_URL: `${process.env.VERCEL_URL || 'http://localhost:3000'}/api/auth`,
        AUTH_SECRET: `${process.env.AUTH_SECRET || 'somestring'}`,
    },
    // ...
// path auth.ts

export const config = {
    // ...
    basePath: "/api/auth",
    trustHost: true
} satisfies NextAuthConfig

export const { handlers, auth, signIn, signOut } = NextAuth(config)

With e.g. VERCEL_URL=http://127.0.0.1:3000

sjoukedv avatar Mar 07 '24 18:03 sjoukedv

@sjoukedv hmm okay so we auto detect the basePath based on VERCEL_URL (among other things), so if that's available even in dev too, like you've set it yourself there, then you don't need a lot of this actually.

Can you try removing auth_url and nextauth_url env vars and basePath config?

ndom91 avatar Mar 08 '24 08:03 ndom91

@sjoukedv hmm okay so we auto detect the basePath based on VERCEL_URL (among other things), so if that's available even in dev too, like you've set it yourself there, then you don't need a lot of this actually.

Can you try removing auth_url and nextauth_url env vars and basePath config?

@ndom91 That redirects me to api/auth/<custom-page>?error=CredentialsSignin&code=<my-custom-error>.

Looks like the page becomes relative to /api/auth instead of the root

    pages: {
        signIn: '/<custom-page>',
        error: '/<custom-page>', // Error code passed in query string as ?error=
    },

If I remove the leading slash frompages.error I get redirected to api/auth<custom-page>?error=CredentialsSignin&code=<my-custom-error> no / after auth. Is the URL properly parsed to be an absolute path instead of relative to /api/auth?

I have also verified the authorized callback is not the issue by always returning true.

sjoukedv avatar Mar 08 '24 12:03 sjoukedv

@sjoukedv hmm okay so we auto detect the basePath based on VERCEL_URL (among other things), so if that's available even in dev too, like you've set it yourself there, then you don't need a lot of this actually. Can you try removing auth_url and nextauth_url env vars and basePath config?

@ndom91 That redirects me to api/auth/<custom-page>?error=CredentialsSignin&code=<my-custom-error>.

Looks like the page becomes relative to /api/auth instead of the root

    pages: {
        signIn: '/<custom-page>',
        error: '/<custom-page>', // Error code passed in query string as ?error=
    },

If I remove the leading slash frompages.error I get redirected to api/auth<custom-page>?error=CredentialsSignin&code=<my-custom-error> no / after auth. Is the URL properly parsed to be an absolute path instead of relative to /api/auth?

I have also verified the authorized callback is not the issue by always returning true.

I think I'm running into a similar issue here.

This is what I currently have for my pages setting

pages: {
  signIn: `/auth`,
  error: `/auth/error`,
},

Both signIn and error are custom pages.

When user gets redirected to error the resulting redirect redirects to /api/auth/auth/error instead of /auth/error. This started to happened after I upgraded from [email protected] to [email protected]

I believe it would be a good idea to make pages route options an absolute url.

CyanFlare avatar Mar 08 '24 13:03 CyanFlare

I've tried adding a custom error but we're using the signIn function from packages/next-auth/src/react.tsx Source and here the code isn't available in the response.

Is it possible to have it added to the response?

I believe it should just be adding the following:

  const error = new URL(data.url).searchParams.get("error")
+ const code = new URL(data.url).searchParams.get("code")

  if (res.ok) {
    await __NEXTAUTH._getSession({ event: "storage" })
  }

  return {
    error,
+   code,
    status: res.status,
    ok: res.ok,
    url: error ? null : data.url,
  } as any

Source

nikcio avatar Mar 18 '24 10:03 nikcio

@CyanFlare @sjoukedv I think both of these are due to an issue we had with AUTH_URL/basePath. This shuold be fixed in main I believe, unfortunately we haven't cut a release yet though. If you're up for testing it in main we'd love some more feedback!

ndom91 avatar Mar 18 '24 17:03 ndom91

next-auth@beta-16 / next.js 14.1.4. A strange error occurs when imorting a classnext-auth@beta-16 / next.js 14.1.4. A strange error occurs when imorting a class

https://github.com/nextauthjs/next-auth/assets/59291123/41a1d677-9122-49f4-bf10-514ed44666c8

MrOxMasTer avatar Mar 25 '24 19:03 MrOxMasTer

@MrOxMasTer Hmm so it's working "as expected" with AuthError, but when yuo extend it from CredentialsSignin you get this odd next/fonts error, did I understand that correctly?

ndom91 avatar Mar 26 '24 15:03 ndom91

@MrOxMasTer Hmm so it's working "as expected" with AuthError, but when yuo extend it from CredentialsSignin you get this odd next/fonts error, did I understand that correctly?

yes

MrOxMasTer avatar Mar 26 '24 16:03 MrOxMasTer

With beta16 this is working for me now (with Next.js 14.2.0-canary.48)

import { CredentialsSignin } from "@auth/core/errors" 

class InvalidLoginError extends CredentialsSignin {
    code = 'Invalid identifier or password'
}



export const config = {
    providers: [
        CredentialsProvider({
            ....
            async authorize(credentials) {
               throw new InvalidLoginError()
            }
        })
    ]

sjoukedv avatar Mar 29 '24 07:03 sjoukedv

With beta16 this is working for me now (with Next.js 14.2.0-canary.48)

Look where I'm importing the class from. It was moved to the main library to avoid downloading "@auth/core", but it doesn't work. But I can say that you have made a good point, that at least some implementation of the class works

MrOxMasTer avatar Mar 29 '24 07:03 MrOxMasTer

@MrOxMasTer Was just trying something with this again in beta.16 and wanted to report that the above did work for me with import { CredentialsSignin } from 'next-auth', but with AuthError it cuased the FE to redirect to a configuration error, while the BE still did log my custom error message / code.

I can't view your video anymore, but it cause anything else weird to happen with [email protected]

ndom91 avatar Apr 18 '24 11:04 ndom91

I've tried adding a custom error but we're using the signIn function from packages/next-auth/src/react.tsx Source and here the code isn't available in the response.

Is it possible to have it added to the response?

I believe it should just be adding the following:

  const error = new URL(data.url).searchParams.get("error")
+ const code = new URL(data.url).searchParams.get("code")

  if (res.ok) {
    await __NEXTAUTH._getSession({ event: "storage" })
  }

  return {
    error,
+   code,
    status: res.status,
    ok: res.ok,
    url: error ? null : data.url,
  } as any

Source

We implemented the login page based on the intercepting logic so we can not enable the redirection. As redirection will move out of the intercepted modal. I want to stick in the same modal. Is it possible to attach the code when redirection is disabled

i also need the above change

LakshanKarunathilake avatar Apr 22 '24 08:04 LakshanKarunathilake