9/ How to add error handling to NextAuth GoogleProvider
Tags: nextauth, nextjs, googleprovider
Published: 2024-04-01 16:21:37

This chapter is a bit tricky because we need to introduce some new concepts. On top of that, `NextAuth` offers very minimal error feedback. This is a bit confusing. We will start with some slightly of topic points and then return to error handling.

The final code for this chapter is available on [github (branch callbacksForGoogleProvider)](https://github.com/peterlidee/NNAS/tree/callbacksForGoogleProvider).

## Next error boundary

We start by adding some error handling in the `Next` frontend by adding `error.tsx` in our root. We use the [basic example from the docs](https://nextjs.org/docs/app/building-your-application/routing/error-handling):

```jsx
// frontend/src/app/error.tsx

'use client'; // Error components must be Client Components

import { useEffect } from 'react';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string },
  reset: () => void,
}) {
  useEffect(() => {
    // Log the error to an error reporting service
    console.error(error);
  }, [error]);

  return (
    <div>
      <h2>Something went wrong!</h2>
      <p>Root error: {error.message}</p>
      <button
        onClick={
          // Attempt to recover by trying to re-render the segment
          () => reset()
        }
      >
        Try again
      </button>
    </div>
  );
}
```

When something goes wrong and there is an uncaught error, this will catch it. This is off topic because `NextAuth` with `GoogleProvider` doesn't produce uncaught errors. But, since this chapter is about errors, we added it here.

## NextAuth signIn callback

We already briefly mentioned this callback in [a previous chapter](https://dev.to/peterlidee/using-nextauth-googleprovider-to-put-a-user-into-the-strapi-database-3eeg): this callback lets you control if a user is allowed to sign in.

```jsx
async signIn({ user, account, profile, email, credentials }) {
  return true
},
```

Why would we need this? Suppose a user made a Google account but didn't verify the account. We would not want this user to connect with our app using this unverified account. Our `signIn callback` has a lot of arguments that we covered before. One of them is profile: the raw user data Google sent back. Profile has a property `email_verified`. We could then do this:

```jsx
async signIn({ user, account, profile, email, credentials }) {
  if(!profile.email_verified) return false;
  return true
},
```

Returning false stops the entire auth flow for this user. But, on top of that, it will redirect the user to the default or custom `NextAuth` error page.

## Creating a custom error page in NextAuth

Besides having a default _sign in_ page (the ugly one from before that we replaced with our custom one), `NextAuth` also has default error page. We can and will replace this default page with a custom error page. Note that this is **not** equal to the error.tsx we made earlier.

When does `NextAuth` use this page? [The docs](https://next-auth.js.org/configuration/pages#error-page) state that the user will be redirected to this error page when there is a:

- `Configuration` error in authOptions.
- `AccessDenied` error: when you restricted access through the `signIn callback`, or `redirect callback`.
- `Verification` error: has to do with the email provider.
- `Default` error: some other cases ... (dunno)

We just saw the `signIn callback`. When it returns false, we are redirected to the error page. So, let's try that, we add this callback:

```jsx
// frontend/src/api/auth/[...nextAuth]/authOptions.ts
{
  async signIn({ user, account, profile, email, credentials }) {
    return false;
  }
}
```

We just want to see the error page so we temporarily return false on everything and try signing in. As expected, we are redirect to the default error page:

![NextAuth default error page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vkfjc0j5gsxa27340pen.png)

And it is in the same _memorable_ style we saw from the default _sign in_ page. But, aren't we missing the error message? No, `NextAuth` put that in the url as an error searchParam: `?error=AccessDenied`.

```
http://localhost:3000/api/auth/error?error=AccessDenied
```

Where is the rest of the message? Nope that's all. We will come back to this. First we finish the section on this error page.

## Custom error page

As with the sign in page, we can also create a custom error page. Create a page:

```jsx
// frontend/src/app/(auth)/authError/page.tsx

type Props = {
  searchParams: {
    error?: string,
  },
};

export default function AuthErrorPage({ searchParams }: Props) {
  return (
    <div className='bg-zinc-100 rounded-sm p-4 mb-4'>
      AuthError: {searchParams.error}
    </div>
  );
}
```

And then tell `NextAuth` about it in authOptions.page:

```jsx
// frontend/src/api/auth/[...nextAuth]/authOptions.ts
  pages: {
    signIn: '/signin',
    error: '/authError',
  },
```

![NextAuth custom error page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6gkpcaoqjceqr00xuzx.png)

We test it out and everything works. `NextAuth` now uses our custom error page. As said, we will deal with the error message later.

## Recap

Let's do a quick recap. We found out that `NextAuth` has an error page. We get redirected to this page when:

1. There is a configuration error (in authOptions) (`/authError?error=Configuration`)
2. We use `signIn` or `redirect callbacks` (read about redirect in [the docs](https://next-auth.js.org/configuration/callbacks#redirect-callback)) (`/authError?error=AccessDenied`)
3. There is a verification error (`/authError?error=Verification`)
4. There are other errors (?) (`/authError?error=Default`)

We replaced the default error page with a custom one. So, we've handled some of the errors we can encounter in `NextAuth`. Before we move on, we need to mention a few more points.

In our `signIn` callback, we returned false when we didn't want the user to be able to authenticate. But, there is another option. We can also return a relative path. This will override the error page and redirect the user to said relative path. Meaning you can redirect them to another route, f.e. specially made for this purpose.

Lastly, let us actually implement the case I gave as an example earlier, with the unverified Google account. We update the `signIn callback`:

```jsx
async signIn({ user, account, profile }) {
  // console.log('singIn callback', { account, profile, user });
  if (
    account &&
    account.provider === 'google' &&
    profile &&
    'email_verified' in profile
  ) {
    if (!profile.email_verified) return false;
  }
  return true;
},
```

(We had to add some type checks because TypeScript was yelling at us.)

## Other errors

Every request we make has the potential to fail or return an error. We should account for this. In our sign in flow thus far, we use 4 requests:

1. call sign in
2. call sign out
3. call Google
4. call Strapi

## Calling sign in and sign out

Internally, `NextAuth` uses a [REST api endpoint](https://next-auth.js.org/getting-started/rest-api) to handle all of it's flow. This means that there are request to this endpoint and potential errors.

I tried to cause an error, f.e. `signIn('foobar', {...})` but nothing happened. No error in the console or terminal and also no error parameter in the url. This let me to conclude that you can safely call `signIn` and `signOut` without trying to catch errors.

## Google OAuth request

When signing in with the `GoogleProvider`, `NextAuth` makes a request to `Google OAuth` at a certain point. As it's a request, it can go wrong. These [OAuth errors](https://developers.google.com/nest/device-access/reference/errors/authorization) can include things like:

- Unverified app
- Invalid token
- Incorrect callback

Just, errors. But, how do we handle these? First, let's create an error. In authOptions, we replace the clientSecret with a random string and see what happens:

```jsx
  //clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? '',
  clientSecret: 'foobar',
```

We run the app and do a sign in. Result is nothing at first glance. We're not logged in, no error pop up, our app doesn't crash and we don't get redirected. But there are some things that happened. Firstly, our url now looks like this:

```
  http://localhost:3000/signin?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2F&error=OAuthCallback
  # after decodeURIComponent
  http://localhost:3000/signin?callbackUrl=http://localhost:3000/&error=OAuthCallback
```

So, the base url is `http://localhost:3000/signin` and we have 2 searchParams: callbackUrl and error. The value of error is 'OAuthCallback'. Secondly, in our frontend terminal, we have a full error showing:

```
[next-auth][error][OAUTH_CALLBACK_ERROR]
https://next-auth.js.org/errors#oauth_callback_error invalid_client (Unauthorized) {
  error: OPError: invalid_client (Unauthorized)
      at processResponse (webpack-internal:///(rsc)/./node_modules/openid-client/lib/helpers/process_response.js:35:19)
      ...
      EDITED: more urls
      ...
      at async Server.requestListener (D:\projecten\dev.to\next-strapi-nextauth\frontend\node_modules\next\dist\server\lib\start-server.js:140:13) {
    name: 'OAuthCallbackError',
    code: undefined
  },
  providerId: 'google',
  message: 'invalid_client (Unauthorized)'
}
```

So, `NextAuth` logged the error as OAUTH_CALLBACK_ERROR while the original Google OAuth error was probably 'invalid_client (Unauthorized)'. That's in the terminal. In our client (browser), `NextAuth` gave us the error parameter: `error=OAuthCallback`. (no error logs in the browser console!)

## NextAuth errors and error codes

`NextAuth` makes a difference between errors and error codes. `OAUTH_CALLBACK_ERROR` is a `NextAuth error` and gets logged in the terminal. `?error=OAuthCallback` is a `NextAuth error code` and it gets put into the callback url as a searchParam.

Any problem that can occur in an auth flow will be caught by `NextAuth` and categorized into a `NextAuth error`. Full list of them can be found in the [docs](https://next-auth.js.org/errors).

On error codes, `NextAuth` says this:

> We purposefully restrict the returned error codes for increased security.

`NextAuth` passes errors as searchParams to 2 pages:

1. The default or custom login page.
2. The default or custom error page.

Above example was an error send as searchParam to our custom sign in page. We made a custom sign in page (`/signin`) and it received an error: `/signin?error=OAuthCallback`. Earlier, we already talked about the errors that are passed to the `NextAuth` error page (f.e. Configuration, AccessDenied).

The [NextAuth docs](https://next-auth.js.org/configuration/pages#error-codes) provide us with a full list of all error codes, split by page (login or error).

## Minimal error messages

So, `NextAuth errors` (terminal) are pretty detailed but we can't use them for user feedback (by design). What is their use then? I'm not sure. To solve problems while developing. To solve problems in production by checking the logs.

Yet, we need some user feedback. `NextAuth` provides us with one word, an error code like `OAuthCallback`, `callback` or `AccessDenied`. So, as a developer, you will have to come up with some clever error messages for each code. f.e.

```jsx
const errorMap = {
  'OAuthCallback': 'A very clever and UX friendly message here.'
  'callback': 'And another one',
  'AccessDenied': 'Even more?',
}

// call them in the frontend
{errorMap[searchParams.error]}
```

So, good luck with that.

## Handling sign in errors in NextAuth

We still need to actually display the errors on the sign in page. Make a new client component:

```jsx
// frontend/src/components/auth/signin/GoogleSignInError.tsx

'use client';

import { useSearchParams } from 'next/navigation';

export default function GoogleSignInError() {
  const searchParams = useSearchParams();
  const error = searchParams.get('error');
  if (!error) return null;
  return (
    <div className='text-center p-2 text-red-600 my-2'>
      Something went wrong! {error}
    </div>
  );
}
```

Note that we don't bother writing a better error message, that's outside the scope of this series. We add this component inside the `<SignIn />` component and we are done:

```jsx
//...
  <GoogleSignInButton />
  <GoogleSignInError />
// ...
```

![sign in error in NExtAuth](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v3ob2eqhx83clotvdzpg.png)

## Handling Strapi errors in NextAuth

There is one thing we haven't checked. Earlier in this article, we mentioned we make 4 requests in our app: sign in and out, Google OAuth and `Strapi`. We are yet to test if a `Strapi` error is handled.

In authOptions, GoogleProvider, remove the incorrect googleClient that we set to 'foobar' earlier to provoke an error. Then, to provoke an error from `Strapi`, we add a random string as the access_token (that we normally get from Google OAuth).

```jsx
// frontend/src/api/auth/[...nextAuth]/authOptions.ts

//`${process.env.STRAPI_BACKEND_URL}/api/auth/${account.provider}/callback?access_token=${account.access_token}`,
`${process.env.STRAPI_BACKEND_URL}/api/auth/${account.provider}/callback?access_token=foobar`,
```

What do we expect? Some kind of `NextAuth error code` in the url. We run the app and try signing in. As expected an error in our url: `error=Callback`. Looking this up in the [docs](https://next-auth.js.org/configuration/pages#sign-in-page):

> Callback: Error in the OAuth callback handler route

Besides this, we also get the real error in the terminal:

```
[next-auth][error][OAUTH_CALLBACK_HANDLER_ERROR]
https://next-auth.js.org/errors#oauth_callback_handler_error 400 Bad Request {
  message: '400 Bad Request',
  ...
}
```

[OAUTH_CALLBACK_HANDLER_ERROR] is how `NextAuth` handled this error. '400 Bad Request' comes from `Strapi`: we threw it here `throw new Error(strapiError.error.message);`.

So, we handled potential `Strapi` errors.

## Conclusion

We learned how `NextAuth` handles errors. In most cases it will just add an error code in the url of the sign in page: `?error=`. In some cases it will redirect to a default or custom `NextAuth` error page, also with an error code. You can look up these code in the [docs](https://next-auth.js.org/configuration/pages#error-codes) and use the error code to give some error feedback to the user.

Besides this, `NextAuth` logs a more detailed error in the terminal of your `Next` server. These also have specific error names that you can look up in the [docs](https://next-auth.js.org/errors). However, you cannot use these for user feedback.

If we step back from the more practical aspects, it is clear that `NextAuth` handles all errors. We don't have to catch anything. This is great. `NextAuth` intentionally limited the error information in the frontend. This can be a bit frustrating and will require an effort to handle. But, in the end, `NextAuth` is very stable. Getting errors using GoogleProvider should be rare and are now handled.

This concludes our work with GoogleProvider. In the rest of this series we will work with the credentials provider.

---

If you want to support my writing, you can [donate with paypal](https://www.paypal.com/donate/?hosted_button_id=4D78YQU4V5NEJ).