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).
User account page
// 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.
// 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:
{
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.
We call this fetcher
inside another function getCurrentUser.ts
passing in the Strapi
token and this should return us our current user.
// 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):
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:
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:
<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:
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
.
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.