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

feat: customizable authorize() error #9871

Merged
merged 10 commits into from
Mar 2, 2024

Conversation

balazsorban44
Copy link
Member

@balazsorban44 balazsorban44 commented Feb 1, 2024

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:

Related: #9099 (see #9099 (comment)). This PR won't close that issue yet, a next-auth PR will be opened after this gets merged

Copy link

vercel bot commented Feb 1, 2024

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

@github-actions github-actions bot added providers core Refers to `@auth/core` labels Feb 1, 2024
Copy link

codecov bot commented Feb 1, 2024

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.

❗ 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 ⚠️
packages/core/src/lib/actions/callback/index.ts 20.00% 4 Missing ⚠️
packages/core/src/errors.ts 94.87% 2 Missing ⚠️
packages/core/src/lib/utils/web.ts 60.00% 2 Missing ⚠️
packages/core/src/types.ts 0.00% 2 Missing ⚠️
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     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@AhmedBaset
Copy link
Contributor

Any updates/plans on when this will be merged?

Copy link
Member

@ndom91 ndom91 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in general LGTM. Curious though, why did you add skip to a few of the core/index.test.ts tests?

@ThangHuuVu
Copy link
Member

@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

@WINOFFRG
Copy link

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.

@ndom91
Copy link
Member

ndom91 commented Mar 4, 2024

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

@ccyen8358
Copy link

ccyen8358 commented Mar 6, 2024

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:

const url = `https://errors.authjs.dev#${this.type.toLowerCase()}`
this.message += `${this.message ? ". " : ""}Read more at ${url}`

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.

@AhmedBaset
Copy link
Contributor

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

@sjoukedv
Copy link

sjoukedv commented Mar 6, 2024

@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.

@AhmedBaset
Copy link
Contributor

@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"

@sjoukedv
Copy link

sjoukedv commented Mar 6, 2024

@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.

@ndom91
Copy link
Member

ndom91 commented Mar 6, 2024

@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 🤔 I've put up a quick prototype PR of that here to hopefully get some discussion going there 🙏

@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.

@sjoukedv
Copy link

sjoukedv commented Mar 7, 2024

@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

@ndom91
Copy link
Member

ndom91 commented Mar 8, 2024

@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?

@sjoukedv
Copy link

sjoukedv commented Mar 8, 2024

@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.

@CyanFlare
Copy link

CyanFlare commented Mar 8, 2024

@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 next-auth@5.0.0-beta.4 to next-auth@5.0.0-beta.15

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

@nikcio
Copy link

nikcio commented Mar 18, 2024

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

@ndom91
Copy link
Member

ndom91 commented Mar 18, 2024

@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!

@MrOxMasTer
Copy link

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

Desktop.20240325.-.22471404_compressed.mp4

@ndom91
Copy link
Member

ndom91 commented Mar 26, 2024

@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?

@MrOxMasTer
Copy link

@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

@sjoukedv
Copy link

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()
            }
        })
    ]

@MrOxMasTer
Copy link

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

@ndom91
Copy link
Member

ndom91 commented Apr 18, 2024

@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 next@14.2.0-canary.58

@LakshanKarunathilake
Copy link

LakshanKarunathilake commented Apr 22, 2024

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

@alejandr0pg
Copy link

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

Es necesario poder obtener el codigo para diferenciar del error.

@dwalker93
Copy link

#10943 comment

@mohdfaizan5
Copy link

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()
            }
        })
    ]

but the problem I came across implementing your solution was I'm not able to use the package from "@auth/core/errors"
instead I tried from

import { CredentialsSignin } from "next-auth";

which is also not giving me expected results,

thankyou in advance for the answer, I'd appreciate if you could tell me the complete way to do custom error

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

Successfully merging this pull request may close these issues.

None yet