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
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
}
}
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'];
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With