ReactHustle

How to Extend User and Session in NextAuth.js (Typescript)

Jasser Mark Arioste

Jasser Mark Arioste

How to Extend User and Session in NextAuth.js (Typescript)

Hello, hustler! Sometimes we need to extend the user and session object in NextAuth.js to get more information about the user. Do you need a role, or a subscribed property to check for access?

In this tutorial, you'll learn how to extend the User and Session object in nextauth.js using Typescript's declaration merging and module augmentation. So that you'll be able to access them when using useSession, getSession , getToken, or anywhere in the application.

The technique used in this tutorial is not only applicable to nextauth but to any library/module that needs it.

What is Declaration Merging? #

I won't delve too much into the details here but in Typescript, it's possible to combine multiple interface definitions into one. For example, if you have the following definitions of the interface Animal:

interface Animal {
   legs: number
}

interface Animal {
   canFly: boolean
}
1234567

Typescript combines both definitions into a single interface so that we'll have access to both properties.

//no error
const cat: Animal = {
  canFly: false,
  legs: 4
}; 
12345

What is Module Augmentation #

Module augmentation is a type of declaration merging where you add or modify definitions to a specific module or library to make it more suitable for your application. This is exactly what we need to modify Nextauth User interface definition.

Enough talk, how do you apply this in nextauth.js?

Augmenting nextauth.js #

For this example, we'll add the role and subscribed properties for both JWT and the User interfaces.  First, create a file in the root directory of your project nextauth.d.ts (the filename can be anything you like)

Create a Role enum:

// nextauth.d.ts
export enum Role {
  user = "user",
  admin = "admin",
}
12345

Augment the next-auth module to modify session.user:

// nextauth.d.ts
declare module "next-auth" {
  interface User {
    role?: Role;
    subscribed: boolean;
  }

  interface Session extends DefaultSession {
    user?: User;
  }
}
1234567891011

Augment the next-auth/jwt module to modify the JWT interface:

// nextauth.d.ts
declare module "next-auth/jwt" {
  interface JWT {
    role?: Role;
    subscribed: boolean;
  }
}
1234567

That's it. You should now be able to access these fields anywhere in your application.

For example, in [...nextauth].ts you should add these fields to the token and session callbacks:

// pages/api/auth/[...nextauth].ts
...
    callbacks: {
      async jwt({ token, user }) {
        /* Step 1: update the token based on the user object */
        if (user) {
          token.role = user.role;
          token.subscribed = user.subscribed;
        }
        return token;
      },
      session({ session, token }) {
        /* Step 2: update the session.user based on the token object */
        if (token && session.user) {
          session.user.role = token.role;
          session.user.subscribed = token.subscribed;
        }
        return session;
      },
    },
...
123456789101112131415161718192021

In your component, you can access the fields when using useSession:

const HomePage: NextPage = () => {
  const { data: session } = useSession();
  console.log(session?.user?.role) // no errors
  return null
}
12345

Refactoring Duplicate Code #

In our augmented modules, you can see there's a bit of duplicate code for User and JWT. Let's create a common interface to solve this code smell. 

Here's the refactored and the full augmented code:

// nextauth.d.ts
import { DefaultSession, DefaultUser } from "next-auth";
export enum Role {
  user = "user",
  admin = "admin",
}
interface IUser extends DefaultUser {
  /**
   * Role of user
   */
  role?: Role;
  /**
   * Field to check whether a user has a subscription
   */
  subscribed?: boolean;
}
declare module "next-auth" {
  interface User extends IUser {}
  interface Session {
    user?: User;
  }
}
declare module "next-auth/jwt" {
  interface JWT extends IUser {}
}
12345678910111213141516171819202122232425

We created a common interface IUser and used it across all the modules.

Caveats #

Just remember that since you're adding fields to JWT, it will increase the size of the nextauth cookie and this will be included in every request.

Conclusion #

We learned how to extend the User and JWT interfaces in the nextauth modules using module augmentation. Module augmentation is an important concept to learn in typescript that you can apply to many packages. Packages and Libraries are also aware of this and provide some interfaces to be extended by the developer.

If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.

Resources #

Credits: Image by Monika Iris from Pixabay

Share this post!

Related Posts