17/ Change password flow in Strapi and NextAuth
Tags: nextauth, nextjs, strapi
Published: 2024-04-01 16:35:27

We have nearly finished. We'll first be creating a user account page. On this page, the user will be able to change the password and edit user data. We will limit this user data to the username but that should get you started. Note that the entire user account page will be guarded. Obviously, only signed in users can view the user account page. Let's make the page itself.

All the code in this chapter is available on [github (branch: changeusername)](https://github.com/peterlidee/NNAS/tree/changeusername).

## User account page

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

import Link from 'next/link';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/authOptions';
import Account from '@/components/auth/account/Account';

export default async function AccountPage() {
  const session = await getServerSession(authOptions);
  if (!session) {
    return (
      <div className='bg-zinc-100 rounded-sm px-4 py-8 mb-8'>
        <h2 className='font-bold text-lg mb-4'>Not allowed</h2>
        <p>
          You need to be{' '}
          <Link href='/signin' className='underline'>
            signed in
          </Link>{' '}
          to view this page.
        </p>
      </div>
    );
  }
  if (session.provider === 'google') {
    return (
      <div className='bg-zinc-100 rounded-sm px-4 py-8 mb-8'>
        <h2 className='font-bold text-lg mb-4'>Account</h2>
        <p>You are logged in to this website with your google account.</p>
      </div>
    );
  }
  return <Account />;
}
```

Quick breakdown: we get a server session from `NextAuth` and display an error when there is no signed in user. When there is a session, we check if the provider is Google. If it is, we return _You are logged in to this website with your google account._. There is no password to change and the username comes from the Google account and we won't allow edits from users using the GoogleProvider. Obviously you would need to fine tune this in a real app. Next, the `<Account />` component.

## <Account />

```jsx
// frontend/src/components/auth/account/Account.tsx

export default function Account() {
  return (
    <div className='bg-zinc-100 rounded-sm px-4 py-8 mb-8'>
      <h2 className='font-bold text-lg mb-4'>Account</h2>

      <div className='mb-8'>
        <h3 className='font-bold mb-4 text-sky-700'>User Data</h3>
        <div className='mb-2'>
          <div className='block italic'>Username: </div>
          <div>Bob</div>
        </div>
        <div className='mb-2'>
          <div className='block italic'>Email: </div>
          <div>bob@example.com</div>
          <div>(You cannot edit your email.)</div>
        </div>
        <div className='mb-2'>Last updated: ----</div>
      </div>

      <div className='mb-8'>
        <h3 className='font-bold mb-4 text-sky-700'>Change password</h3>
        [change password component]
      </div>
    </div>
  );
}
```

This is a hardcoded page for now with 2 sections: user data and change password. In user data we display the username, email and last updated info. Note that in `Strapi` we shouldn't edit the email as it counts as a unique identifier. The change password component will be a form that we handle later.

Where do we get our user data from? You could be tempted to get it from a `nextAuth` session but that is incorrect! It would work in this case but in real life you would have more data like address, preferences, email subscriptions, ... You wouldn't add all those to your `NextAuth` session.

So, we need to make a api call to `Strapi`. There is a `Strapi` endpoint for this: `/users/me`. This is a GET request so it doesn't take a body. It does need headers:

```ts
{
  headers: {
    Authorization: `Bearer ${token}`,
  },
}
```

This is the basic Authorization header you have to add if you query `Strapi` for non public content. What is token? It is the `Strapi` token that should be inside our `NextAuth` session. The token is how `Strapi` verifies that the user is actually an authenticated user. If you know `Strapi` basics, this should be clear. Note that we could also add parameters to this endpoint like f.e. populate.

## fetch currentUser in Strapi

I created a helper function `fetcher.ts`. This function takes a path, parameters (object with f.e. populate) and options (object with f.e. Authorization headers). It then constructs a url using `qs` and makes the request to `Strapi`. On success it will return the `Strapi` data, on error it will throw an error that will get caught by our root `error.tsx` file. It also adds Types and so. You can see the [`fetcher` function on github](https://github.com/peterlidee/NNAS/blob/changeusername/frontend/src/lib/fetchData/fetcher.ts).

We call this `fetcher` inside another function `getCurrentUser.ts` passing in the `Strapi` token and this should return us our current user.

```ts
// frontend/src/lib/fetchData/getCurrentUser.ts

import { StrapiCurrentUserT } from '@/types/strapi/StrapiCurrentUserT';
import fetcher from './fetcher';

export async function getCurrentUser(token: string) {
  const options = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
    next: { tags: ['strapi-users-me'] },
  };
  const user: StrapiCurrentUserT = await fetcher('/users/me', {}, options);
  return user;
}
```

Note the line `next: { tags: ['strapi-users-me'] }`. This is a `Next` tag that will allow up to revalidate this fetch later on. We now call `getCurrentUser` inside our `<Account />` component (= server component):

```jsx
  const session = await getServerSession(authOptions);
  const currentUser = await getCurrentUser(session!.strapiToken!);
  console.log('currentUser', currentUser);
```

Note the `!` in `await getCurrentUser(session!.strapiToken!)`. That's how we tell TypeScript that there will be a session (we're in a guarded component) and there will be a strapiToken. This is the log:

```js
currentUser {
  id: 2,
  username: 'Bob',
  email: 'bob@example.com',
  provider: 'local',
  confirmed: true,
  blocked: false,
  createdAt: '2024-03-15T10:59:28.300Z',
  updatedAt: '2024-03-15T10:59:28.300Z'
}
```

We use the `currentUser` to populate the user data in our `<Account />` component:

```jsx
<div className='mb-8'>
  <h3 className='font-bold mb-4 text-sky-700'>User Data</h3>
  <div className='mb-2'>
    <div className='block italic'>Username: </div>
    <div>{currentUser.username}</div>
  </div>
  <div className='mb-2'>
    <div className='block italic'>Email: </div>
    <div>{currentUser.email}</div>
    <div>(You cannot edit your email.)</div>
  </div>
  <div className='mb-2'>
    Last updated: {new Date(currentUser.updatedAt).toLocaleString()}
  </div>
</div>
```

And our `<Account />` component now looks like this:

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

Quick recap. To populate our account page, we don't use data from a `NextAuth` session. Instead we use a `Strapi` endpoint `/users/me` that retrieves the current user's data when a `Strapi token` is included. We call this endpoint using the `getCurrentUser` function that in turn calls the `fetcher` function. In case of error, `Next` error boundary will catch it. If successful, we use it to display data on the account page.

One last detail. To actually get to the account page, we update `<NavbarUser />` so the user's name becomes the link to `/account`.

![link to account page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcfffkyotxxe318ks8a1.png)

## Conclusion

We will leave it at this for this chapter. In the next chapter, we will implement the edit user name functionality.

---

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