7/ NextAuth callback functions explained
Tags: nextauth, callbacks, nextjs
Published: 2024-04-01 16:19:58

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

`NextAuth` creates a `JWT token` and sets that in a cookie on our client (browser). On top of that, `NextAuth` lets us read this token by using either the client component `useSession` hook or the server component `getServerSession` function.

`NextAuth` also gives us the tools to customize what we put on this token and what we get from the session. The session is what `useSession` or `getServerSession` returns. These tools are called callbacks and we define them in our authOptions object.

## Overview

There are four callbacks:

1. signIn
2. redirect
3. jwt
4. session

The `jwt callback` is responsible for putting data onto the `JWT token`. `NextAuth` puts data on the token by default. We can use the `jwt callback` to put other data on the token, customizing this process.

As you might guess, the `session callback` handles what is put on the session (the return from `useSession` or `getServerSession`). Again, we can customize these values.

We won't be using the other 2 callbacks. Note that the `signIn callback` is not the same as the `signIn` function we used earlier to redirect us to the _sign in_ page or start the auth flow. The `signIn callback`'s purpose is to controle if a user is allowed to _sign in_.

Finally, the `redirect callback` lets us customize the redirect behaviour on f.e. signing in. You can read more about these last two callbacks in the [NextAuth docs](https://next-auth.js.org/configuration/callbacks).

## Strapi

Before we start coding, let's think about what we need to do. There's 2 things we need to achieve. Firstly, somewhere in our _sign up_/_sign in_ process with Google provider, we want to put the user into the `Strapi` database as a `User`.

Secondly, when we create a `Strapi` `User`, `Strapi` will generate an `JWT access token` for this user. When we request non public content from `Strapi` we send this access token along as a header so `Strapi` can authenticate the user. Only authenticated users can request non public content.

Where do we save this `Strapi` token? Inside the `JWT token`. So, we will put the `Strapi JWT token` inside the `NextAuth JWT token`. This is something we need to configure. We need to customize the `NextAuth token` and that is our second goal.

## authOptions

Before we get into the callbacks I'm gonna first put some other settings in authOptions. We add these 2 properties:

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

{
  // ...
  session: {
    strategy: 'jwt',
  },
  secret: process.env.NEXTAUTH_SECRET,
  // ...
}
```

These are actually just the defaults but I like to explicitly set them. The session strategy indicates that we are using `JWT tokens`. The alterative would be `database` but that is a whole other story. The secret just refers to our .env file that we created earlier.

## The callback syntax

In authOptions, add callback property with the `session` and `jwt` callbacks:

```ts
// frontend/src/api/auth/[...nextAuth]/authOptions.ts
{
  // ...
  callbacks: {
    async jwt({ token, trigger, account, profile, user, session }) {
      // do some stuff
      return token;
    },
    async session({ token, user, session, newSession, trigger }) {
      // do some stuff
      return session;
    },
  },
  // ...
}
```

This is the syntax from the docs and initially it really confused me. Are we calling this function? But then why does it have a body? It played tricks with my mind. I managed to understand was going on by writing it _longhand_:

```js
{
  callbacks: {
    jwt: async function jwt({ token, user, account, profile, session, trigger }) {
      // do stuff
      return token;
    },
  }
}
```

I also had to recall what a callback function actually does. Consider this example:

```js
// the would be the part NextAuth is doing
const myArr = [1, 2, 3, 4];
myArr.map(myCallback);

// this would be one of our callbacks in authOptions
function myCallback(item) {
  console.log(item);
}
```

Given this example, it should be clear where the arguments come from in our `jwt` and `session` callbacks: `NextAuth` just passes them when it calls the callback and that is how we have access to the parameters in our function body.

That was just a side note, let's get back to customizing. Note that we will be using the shorthand like in the docs.

## NextAuth jwt callback

`jwt` is called every time we create a token (by signing in) or when we update or read a token (f.e. using `useSession`). This callback is used to put extra information on the `JWT token` that `NextAuth` creates. It must always return the token argument.

The callback takes a lot of arguments and that's confusing but there is some good news also:

- We don't need all of these arguments so we will be leaving some out. Yay!
- We're running in TypeScript and when we hover the arguments, we get an explanation of what they are.

To really understand these arguments it helps to put them in sequence. Except for token, these arguments will usually be undefined. Only on certain events like `sign in` or `update` will they be populated with data. This makes sense, `NextAuth` makes an auth request on singing in. When the user is already logged in, there is no need to call the provider.

1. token
2. trigger
3. account
4. profile
5. user
6. session

Token is always populated. When there is a certain event - a trigger - like signing in, `NextAuth` will go to the provider. The provider info will be saved in the account argument. The provider sends over user info, this will be safed in profile argument. `NextAuth` cleans this up and saves some properties into user argument. The session is the exception because it only gets populated on an 'update' event (trigger). Careful, it is not related to the `NextAuth` session (returned from f.e. `getServerSession`) as one could expect. All of these arguments come together to populate the token.

I hope this makes sense. It's a flow of different stages and data in the auth process. Let's us take a short look at each of them:

### 1. Token argument

Token is what will be put on our `JWT token`. Right now, it holds the values that `NextAuth` puts on them by default:

```ts
{
  name: 'Peter Jacxsens',
  email: string,

  // ignore the rest
  /*
  picture: string,
  sub: string,
  iat: number,
  exp: number,
  jti: string
  */
},
```

In our case we just ignore everything but name and email. So, this is what `NextAuth` puts on our token by default.

### 2. Trigger argument: "signIn" | "signUp" | "update" | undefined

Trigger is like an `NextAuth` event. When the user is already signed in it is undefined. We will use this later on.

### 3. Account argument

Account contains provider info when certain triggers happen. When signing in with GoogleProvider it will look like this:

```ts
{
  // this we use
  provider: 'google',
  access_token: string,

  // the rest we don't need
  /*
  type: 'oauth',
  providerAccountId: string,
  expires_at: string,
  scope: string,
  token_type: 'Bearer',
  id_token: string
  */
}
```

### 4. Profile argument

This is the raw user information we get back from our provider. It's only populated on _sign in_ and undefined else. `NextAuth` uses this to create a User. We don't use it and hence ignore it.

```ts
{
  /*
  iss: 'https://accounts.google.com',
  azp: string,
  aud: string,
  sub: string,
  email: string,
  email_verified: boolean,
  at_hash: string,
  name: 'Peter Jacxsens',
  picture: string,
  given_name: string,
  family_name: string,
  locale: string,
  iat: number,
  exp: another,
  */
}
```

### 5. User argument

This is like a cleaned up version of profile. It will only be populated on _signing in_.

```ts
{
  id: string,
  name: 'Peter Jacxsens',
  email: string,
  image: string
},
```

Remember, this is a flow. The token will be populated with some things by default. When there is a trigger, there will be some raw provider data in account and some raw user data in profile. `NextAuth` takes some data from profile and puts it into the user argument. Here is a concrete (but useless example) of the flow:

```
account.providerAccountId -> profile.sub -> user.id -> token.sub
```

### 6. session argument

We will cover this one later when working with the update trigger.

### Recap

Let's do a quick recap. We're talking about the `jwt callback`. It gets called when there is a special event like sign in but also every time `useSession` or `getServerSession` is used. The main goal of this callback is populating the `NextAuth` `jwt token`. It's a callback function that exposes a whole series a arguments to us: token will always be populated. On certain triggers, other arguments will be populated. We can listen for these triggers and then conditionally customize the token.

## NextAuth session callback

Let's turn to our second callback: `session`. The goals of this callback is simple, we populate what gets returned from the `useSession` hook and the `getServerSession` function. So, we use the session callback to customize our `NextAuth` session. Here is our callback once more. Note that we must always return session from this callback:

```ts
async session({ token, user, session, newSession, trigger }) {
  // do some stuff
  return session;
},
```

As with the `jwt callback`, the token argument will always be populated. In fact, token here equals the return value from the `jwt callback`. `jwt` is called first, followed by the `session callback`. This is what the token argument looks like:

```ts
{
  name: 'Peter Jacxsens',
  email: string,

  // ignore the rest
  /*
  picture: string,
  sub: string,
  iat: number,
  exp: number,
  jti: string
  */
},
```

Inside the `session callback`, the session argument is also always populated, regardless of wether we're are signed in or signing in. Session argument:

```ts
{
  user: {
    name: 'Peter Jacxsens',
    email: string,
    image: string,
  },
  expires: Date
}
```

We have seen this before when we console.logged `useSession` or `getServerSession`. This is what `NextAuth` puts into our session by default when running `GoogleProvider`. This of course is the whole point!

The other arguments of the `session callback`: user and newSession are only available when using authOptions `session.strategy === database`. (We use session.strategy jwt) Hence we will ignore them. We will ignore trigger aswell as we don't need it.

So, next time we use the `session callback` it will look like this: `async session({ token, session }) {}`. And that's all.

## Wrapping up

We just had a deep dive into the `jwt` and `session` callback functions. In the next chapter we will start using these to solve the 2 problems we started this chapter with:

1. Putting a user in `Strapi`
2. Putting a `Strapi` `jwt token` onto our `NextAuth jwt token`

With all the theory from above, you will probably have a vague clue as how we will do this. Don't worry if you forget about all these arguments. You just need to struggle with them as you are customizing the token or session.

Finally, I updated both the `jwt` and `session` callbacks with a log function, so you can easily check the logs and see what's what. These do flood your terminal so comment them out when done.

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

{
  callbacks: {
    async jwt({ token, trigger, profile, user, session }) {
      console.log('jwt callback', {
        token,
        trigger,
        profile,
        user,
        session,
      });
      return token;
    },
    async session({ token, session }) {
      console.log('session callback', {
        token,
        session,
      });
      return session;
    },
  },
}
```

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