Extend the Session Object in NextAuth

By default, NextAuth returns minimal information in the session object. However, there are use cases where you might want to return extra information to the client.

This post shows you how to extend the session object and return additional fields to the client.

NextAuth Callbacks

NextAuth callbacks enable you to control what happens when certain actions are performed (sign in, redirect, client accesses a session, etc.) in the application.

NextAuth has 4 callbacks:

  • signIn(): This is triggered when a user signs in.
  • redirect(): This is triggered whenever NextAuth.js has to redirect a user.
  • session(): This is triggered whenever a session is checked, either via the useSession, getSession, or session callbacks.
  • jwt(): This is triggered whenever a JSON Web Token is created or updated.

In our case, we're interested in the "session" callback, which is called whenever the client checks a session. So, you can use this callback to pass extra information to the client, which is what we'll do.

JWT Callback

Before the data is available in the session callback, you must add it to the token in the jwt() callback.

The jwt() callback is triggered whenever a JWT token is created or updated. For example, a JWT token is created when the user signs in or updated when a session is accessed in the client.

Recently, I worked on the "email verification" feature at Documenso, and I had to expose an extra field - emailVerified - to the client. The emailVerified field represents whether the users have verified their email or not.

The logic of adding emailVerified in the session object is as follows:

  1. merge the 2 objects - token and user
  2. add the emailVerified property to the merged object
  3. return the new object
callbacks: {
    async jwt({ token, user }) {
      const merged = {
        ...token,
        ...user,
      };
      
      ....

      return {
        id: merged.id,
        name: merged.name,
        email: merged.email,
        lastSignedIn: merged.lastSignedIn,
        emailVerified: merged.emailVerified,
      };
    },

Since the jwt() callback is called before the session() callback, anything that you add to the JWT token is available in the session callback. That means you can now send it to the client in the session object.

Session Callback

The last step involves returning the emailVerified in the session object as follows:

session({ token, session }) {
      if (token && token.email) {
        return {
          ...session,
          user: {
            id: Number(token.id),
            name: token.name,
            email: token.email,
            emailVerified:
              typeof token.emailVerified === 'string' ? new Date(token.emailVerified) : null,
          },
        } satisfies Session;
      }

      return session;
    },

The emailVerified field in the code checks if the email verification date is a string and, if so, converts it to a Date object. Otherwise, it sets it to null.

A screenshot of the code that extends the session object in NextAuth

Now, you can check if the user has a verified email by accessing the emailVerified value. In our case, we want to disable the ability to upload documents if the user doesn't have a verified email.

<DocumentDropzone
    className="min-h-[40vh]"
    disabled={remaining.documents === 0 || !session?.user.emailVerified}
    onDrop={onFileDrop}
/>

The above code snippet shows the emailVerified field being used to determine whether the user can upload documents or not.

All the code shown in this post is available in this pull request. You can also see the full implementation of the email verification in this PR.