Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Next Auth: Extending User schema to include new fields

I have a typescript application with Next Auth v4 that's using GithubProvider + MongoDBAdapter (this way I have access to the database documents User, Profile and Account).

The problem is that I need to add a new field to User schema, for example the role field.

The majority of results that I found online point that in v4 you need profile a function profile to your provider.

And so I did! This is my [...nextauth].ts

import NextAuth from 'next-auth'
import GithubProvider from 'next-auth/providers/github'
import { MongoDBAdapter } from '@next-auth/mongodb-adapter'
import connectDB from 'lib/mongooseConnect'
import mongoose from 'mongoose'

connectDB()

export const authOptions = {
   // Configure one or more authentication providers
   providers: [
      GithubProvider({
         clientId: process.env.GITHUB_ID,
         clientSecret: process.env.GITHUB_SECRET,
         //@ts-ignore
         profile(profile) {
            return {
               id: profile.id,
               name: profile.name,
               email: profile.email,
               image: profile.avatar_url,
               role: 'USER',
            }
         },
      }),
      // ...add more providers here
   ],
   adapter: MongoDBAdapter(
      new Promise((resolve) => resolve(mongoose.connection.getClient()))
   ),
}

export default NextAuth(authOptions)

This allowed me to populate a default field in User document... but when I try to access it by session.user.role I get a TS error as an undefined result.

For example, this code doesn't work:

import React from 'react'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/router'

import useProfileByOwner from 'hooks/api/useProfileByOwner'
import { IProfile } from 'models/Profile'

const UserContext = React.createContext<Value>({
   profile: undefined,
   isSelected: undefined,
})

export const UserProvider = ({ children }) => {
   const { data: session } = useSession()
   const { data: ownProfile } = useProfileByOwner(session?.user?.email)
   const router = useRouter()

   //@ts-ignore
   const isSelected =
      router.query.slugOrId === ownProfile?.slug ||
      router.query.slugOrId === ownProfile?._id ||
      //@ts-ignore
      session?.user?.role === 'ADMIN'

   return (
      <UserContext.Provider value={{ profile: ownProfile, isSelected }}>
         {children}
      </UserContext.Provider>
   )
}

type Value = {
   profile: IProfile
   isSelected: boolean
}

export default UserContext

like image 910
Wagner Paz Avatar asked Oct 22 '25 11:10

Wagner Paz


2 Answers

Since I've had to deal with this same issue, out of completeness and building upon Jeff's answer, here's a complete example so that the TS compiler will fully comply:

// types/next-auth.d.ts
import NextAuth, { DefaultSession } from "next-auth"
import { JWT } from "next-auth/jwt"

declare module "next-auth" {
  /**
   * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
   */
  interface Session {
    user: {
      /** The user's role. */
      role: string
    } & DefaultSession["user"]
  }
  interface User {
    role: string
  }
}

declare module "next-auth/jwt" {
  /** Returned by the `jwt` callback and `getToken`, when using JWT sessions */
  interface JWT {
    /** The user's role */
    role: string
  }
}
like image 57
Nelloverflow Avatar answered Oct 25 '25 02:10

Nelloverflow


It looks like you're wanting to Extend default interface properties.

By default, TypeScript will merge new interface properties and overwrite existing ones. In this case, the default session user properties will be overwritten, with the new one defined above.

You'll need to declare the type extension in order for TypeScript to recognize session.user.role as a valid field.

// ./types/nextauth.d.ts
import NextAuth from 'next-auth';

declare module 'next-auth' {
    interface Session {
        user: {
            role: string;
        } & DefaultSession['user'];
    }
}
like image 37
Jeff Wesson Avatar answered Oct 25 '25 00:10

Jeff Wesson