13/ Signing up with the NextAuth CredentialsProvider
Tags: nextauth, nextjs, credentialsprovider
Published: 2024-04-01 16:31:45

Before we start coding there's a problem we need to handle with GoogleProvider and then we need to determine how we will setup the sign up flow.

This code for this example is available on [github: branch credentialssignup](https://github.com/peterlidee/NNAS/tree/credentialssignup).

## GoogleProvider limitations

When using the GoogleProvider with `NextAuth` there is only one flow. If the user has never connected to the app before, said user will be automatically added in the `Strapi` DB by our `NextAuth` setup. This means that there is no difference between the sign in and sign up flow for the GoogleProvider. One button signs in and up.

But we will have 2 pages, sign in and up. Logically we would then just use the one button on both pages, yes? No, there is a problem with this. We implemented [error handling for our GoogleProvider](https://dev.to/peterlidee/error-handling-in-our-form-component-for-the-nextauth-credentialsprovider-1ll2):

> In most cases `NextAuth` 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.

This means that when there is an error in our GoogleProvider on our sign up page, `NextAuth` will redirect us to the sign in page and display the error there. On top of that, `NextAuth` does not have a sign up page that we can register. (pages option in our authOptions object)

In other words, we can only use the _sign in with Google_ button on the sign in page. If we use it on another page like sign up, the error handling won't work as desired - we would like error message on the sign up page, not be redirected to the sign in page.

So ... this is a problem. Worse yet, we can't fix it.

## GoogleProvider solution?

We haven't really defined an end goal for our app: a specific auth setup we want to build. This is a problem I stumbled into. It's a limitation, an unpleasant one, that `NextAuth` imposes. For me, in this situation, it is not the biggest problem because I'm just making a tutorial. But, if you're working for a client who has specific demands, this will be an issue! The point here is: know that your carefully crafted UX prototypes may run into these kind of issues. There will be limitations.

Possible solutions here:

1. GoogleProvider errors will be rare, so just go ahead? No, bad UX.
2. Remove GoogleProvider error handling? No, will resort to defaults.
3. Leave out the _sign in with Google_ button on the sign up page?

The most elegant solution I came across was to rework the sign in page using tabs.

![wireframe](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1i9hbfc4mecnjijfggts.png)

One tab for sign in and one for sign up. Below that the _sign in with Google_ button that's always visible. This would be our sign in page so the GoogleProvider error handling would work.

But, this all is beyond the scope of this series. We will be building a separate sign up page while leaving out the _sign in with Google_ button. Our focus is the show how to build a sign up flow for the CredentialsProvider.

## Sign up flow in Strapi

`Strapi` has an endpoint for signing up new users with credentials.

```jsx
const strapiResponse = await fetch(
  process.env.STRAPI_BACKEND_URL + '/api/auth/local/register',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ username, email, password }),
    cache: 'no-cache',
  }
);
```

So, a POST request to `/api/auth/local/register` that passes the username, email and password in an object. The `Strapi` response depends on your `Strapi` settings. If you have email confirmation disabled, then `Strapi` will

- Add this user into the database (Authorized)
- Return the DB user with a `Strapi JWT token`.

If you have email confirmation enabled, `Strapi` will

- Add this user into the database (not confirmed)
- Send an email (if you made the [correct setup](https://dev.to/peterlidee/next-nextauth-strapi-setting-up-our-project-4jc7)) to this user asking for confirmation
- Return the DB user without a token!

```jsx
{
  "user": {
    "id": 4,
    "username": "Peter ",
    "email": "someEmail",
    "provider": "local",
    "confirmed": false,
    "blocked": false,
    "createdAt": "2024-03-19T14:23:42.478Z",
    "updatedAt": "2024-03-19T14:23:42.478Z"
  }
}
```

A quick reminder: when signing in, we put this `Strapi token` into our `NextAuth token`. We can then make requests to `Strapi` endpoints for non public data by passing along the `Strapi token` that we read from our `NextAuth token` using `useSession` or `getServerSession`. No token, no authorization to make requests. This makes sense. We will deal with the implications this has for `NextAuth` in a bit.

It's also possible that `Strapi` returns an error. For example when the username or email has already been taken. `Strapi` then returns an error in the the standard `Strapi` error format that we saw before:

```jsx
{
  "data": null,
  "error": {
      "status": 400,
      "name": "ApplicationError",
      "message": "Email or Username are already taken",
      "details": {}
  }
}
```

The confirmation email flow will be handled later. We now know how the `Strapi` signup endpoint works.

## CredentialsProvider sign up flow

There is a wide range of UX patterns on how to do a sign up. Each of these requires a different setup and I can't cover them all. This is the approach we will take here:

1. User goes to sign up page
2. Enters credentials: username, email and password.
3. Signing up does not sign you in.
4. Signing up creates an unauthorized user in `Strapi`.
5. `Strapi` sends an auth email.
6. User clicks link and goes to confirmation page
7. `Strapi` auths the user.
8. User is prompted to sign in.

And then somewhere in between these we also need to add an option for the user to request a new confirmation email.

It's not a bad flow but it has some problems. I know this is probably not the flow you want to build. But, you can learn from this and refactor it into your own flow.

## NextAuth

How does `NextAuth` fit into this sign up flow? We briefly mentioned using a second CredentialsProvider but that is for a different auth flow. `NextAuth` is basically for setting and reading out a `NextAuth token`.

However, in our auth flow, signing up won't sign you in. Confirming your email won't sign you in either. The only thing that will sign you in is _signing in_, using the sign in page. Therefore, we won't need `NextAuth`.

This will make full sense once we started building the actual sign up components.

## Strapi setup

Run your backend and open up `Strapi` admin:

```
settings > Users & Permissions plugin > Advanced settings
```

- Default role for authenticated users -> authenticated
- Enable email confirmation -> true
- Redirection url -> http://localhost:3000/confirmEmail (this is for later)
- Save

## Sign up page

We will be reusing the structure and styling from our sign in page as much as possible. Remember, we will be leaving out the _sign in with Google_ button. Create a page, a `<SignUp />` and `<SignUpForm />` component.

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

import SignUp from '@/components/auth/signup/SignUp';

export default function SignUpPage() {
  return <SignUp />;
}
```

`<SignUp />` is a server component. It just contains some html and tailwind, you can see it on [github](https://github.com/peterlidee/NNAS/blob/credentialssignup/frontend/src/components/auth/signup/SignUp.tsx). Inside `<SignUp />` we will import and render our `<SignUpForm />` component where all the action happens. `Strapi` requires 3 input fields for signing up a frontend user: username, email and password. So, we will have a form with these 3 fields and a button. This will be our starting point.

![signup page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qgtgoxgg2bnp7sqbigwy.png)

```jsx
// frontend/src/components/auth/signup/SignUpForm.tsx

export default function SignUpForm() {
  return (
    <form className='my-8'>
      <div className='mb-3'>
        <label htmlFor='username' className='block mb-1'>
          Username *
        </label>
        <input
          type='text'
          id='username'
          name='username'
          required
          className='border border-gray-300 w-full rounded-sm px-2 py-1'
        />
      </div>
      <div className='mb-3'>
        <label htmlFor='email' className='block mb-1'>
          Email *
        </label>
        <input
          type='email'
          id='email'
          name='email'
          required
          className='bg-white border border-zinc-300 w-full rounded-sm p-2'
        />
      </div>
      <div className='mb-3'>
        <label htmlFor='password' className='block mb-1'>
          Password *
        </label>
        <input
          type='password'
          id='password'
          name='password'
          required
          className='bg-white border border-zinc-300 w-full rounded-sm p-2'
        />
      </div>
      <div className='mb-3'>
        <button
          type='submit'
          className='bg-blue-400 px-4 py-2 rounded-md disabled:bg-sky-200 disabled:text-gray-400 disabled:cursor-wait'
        >
          sign up
        </button>
      </div>
    </form>
  );
}
```

We continue in the next chapter.

---

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