for next-auth how to include a different model to a User Object

user {
id: 'cla6t4y450000rpsvp6eijgdh',
name: 'Batman',
emailVerified: null,
image: 'https://lh3.googleusercontent.com/a/ALm5wu0MqN4wvAyLfGEz5Q6JlttF_yrj5Mti4NUWX-qh=s96-c',
createdAt: 2022-11-07T13:16:46.132Z,
updatedAt: 2022-11-07T13:16:46.132Z
}
user {
id: 'cla6t4y450000rpsvp6eijgdh',
name: 'Batman',
emailVerified: null,
image: 'https://lh3.googleusercontent.com/a/ALm5wu0MqN4wvAyLfGEz5Q6JlttF_yrj5Mti4NUWX-qh=s96-c',
createdAt: 2022-11-07T13:16:46.132Z,
updatedAt: 2022-11-07T13:16:46.132Z
}
What i want
user {
id: 'cla6t4y450000rpsvp6eijgdh',
name: 'Batman',
emailVerified: null,
image: 'http://example.com',
teacher: {} <----- another object
createdAt: 2022-11-07T13:16:46.132Z,
updatedAt: 2022-11-07T13:16:46.132Z
}
user {
id: 'cla6t4y450000rpsvp6eijgdh',
name: 'Batman',
emailVerified: null,
image: 'http://example.com',
teacher: {} <----- another object
createdAt: 2022-11-07T13:16:46.132Z,
updatedAt: 2022-11-07T13:16:46.132Z
}
so that i can attach the user. teacher in the
async session({ session, user }) {
if (session.user) {
session.user.id = user.id;
session.user.teacher = user.teacher;
}

return session;
},
async session({ session, user }) {
if (session.user) {
session.user.id = user.id;
session.user.teacher = user.teacher;
}

return session;
},
The goal is to use that teacher object to verify certain things. Thanks
39 Replies
Lopen
Lopen3y ago
1:
"/src/types/next-auth.d.ts"

declare module "next-auth" {
interface Session {
user: {
...,
teacher: {},
} & DefaultSession["user"];
}
}
"/src/types/next-auth.d.ts"

declare module "next-auth" {
interface Session {
user: {
...,
teacher: {},
} & DefaultSession["user"];
}
}
2:
"/src/pages/api/auth/[...nextauth].ts"

async session({ session, user }) {
if (session.user) {
session.user.id = user.id;
session.user.teacher = user.teacher;
}

return session;
},
"/src/pages/api/auth/[...nextauth].ts"

async session({ session, user }) {
if (session.user) {
session.user.id = user.id;
session.user.teacher = user.teacher;
}

return session;
},
kacakthegreat
kacakthegreatOP3y ago
Oh thanks for the answer, I think i need to be more specific next time
user: {
...,
teacher: {},
} & DefaultSession["user"];
user: {
...,
teacher: {},
} & DefaultSession["user"];
The above code solves the problem which it will include the teacher object but if you take a look at my model
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
teacher Teacher?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
teacher Teacher?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
I added a relation to a teacher model So even i added the teacher object to the session, it will undefined
julius
julius3y ago
You need to fetch it from db in your createContext I believe?
kacakthegreat
kacakthegreatOP3y ago
Where do i do that @julius Thanks for helping Im assuming its here
export const createContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;

// Get the session from the server using the unstable_getServerSession wrapper function
const session = await getServerAuthSession({ req, res });

return await createContextInner({
session,
});
};
export const createContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;

// Get the session from the server using the unstable_getServerSession wrapper function
const session = await getServerAuthSession({ req, res });

return await createContextInner({
session,
});
};
export const createContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;

// Get the session from the server using the unstable_getServerSession wrapper function
const session = await getServerAuthSession({ req, res });

if (session) {
const user = await prisma.user.findUnique({
where: {
id: session?.user?.id,
},
include: {
teacher: true,
},
});

console.log();

session.user!.teacher = user?.teacher;

console.log("session user", user);
console.log("session teacher", session.user);
}

console.log("final session", session);

return await createContextInner({
session,
});
};
export const createContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;

// Get the session from the server using the unstable_getServerSession wrapper function
const session = await getServerAuthSession({ req, res });

if (session) {
const user = await prisma.user.findUnique({
where: {
id: session?.user?.id,
},
include: {
teacher: true,
},
});

console.log();

session.user!.teacher = user?.teacher;

console.log("session user", user);
console.log("session teacher", session.user);
}

console.log("final session", session);

return await createContextInner({
session,
});
};
but it didnt attach it to the
useSession()
useSession()
@julius
julius
julius3y ago
The useSession uses the object from the session callback you changed previously. But imo I wouldn’t attach to much stuff to the useSession hook
kacakthegreat
kacakthegreatOP3y ago
Tried doing it in the callback session also but i dont want to call prisma there haha
julius
julius3y ago
Just do a user.me procedure or smth And you can attach the teacher there The session object is stored in cookies (I think?) and you don’t want too big object there
kacakthegreat
kacakthegreatOP3y ago
Sorry i dont understand the user.me procedure part . Do you have any examples that you can share if you dont mind
julius
julius3y ago
Gimme sec at my pc soon
kacakthegreat
kacakthegreatOP3y ago
Okay julius take ur time, thanks for helping
julius
julius3y ago
me: protectedProcedure.query(({ ctx }) => {
const userWithExamples = ctx.prisma.user.findMany({
where: { id: ctx.session.user.id },
include: { examples: true },
});

return userWithExamples;
}),
me: protectedProcedure.query(({ ctx }) => {
const userWithExamples = ctx.prisma.user.findMany({
where: { id: ctx.session.user.id },
include: { examples: true },
});

return userWithExamples;
}),
kacakthegreat
kacakthegreatOP3y ago
This is under trpc router right?
julius
julius3y ago
yea i placed it just in the approuter now but if you have a user router that's prob better consider that the createContext function is called on every request so you want it to be as minimal as possible to not block unnecessary
kacakthegreat
kacakthegreatOP3y ago
Okay understood, the thing is i need the teacher object to present in the session globally so that i can use it whenever i can like in Navbar etc... so example
{session.user.teacher ? (<>Teacher</>) : (<>Sign up as a teacher</>)}
{session.user.teacher ? (<>Teacher</>) : (<>Sign up as a teacher</>)}
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
kacakthegreat
kacakthegreatOP3y ago
Thanks for sharing! but the problem is about relational data, seems like even if you extend the session it wont be included because i believe we need to @Jan Gryga include : { teacher: true } then only we can access the teacher object, so im still looking around
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
julius
julius3y ago
You can override the prisma adapter
julius
julius3y ago
Small snippet
julius
julius3y ago
Or include the teacherId in session (should be possible since it’s a primitive?) and fetch the rest in tRPC context
kacakthegreat
kacakthegreatOP3y ago
i tried to do the latter where i include the teacherId in session but in my case i have to call prisma there which is not ideal. but will look into overriding the adapter i think thats what i need but is there like an official guide to override the adapter? example should i call linkAccount somewhere in the code base? I could not find the official guide
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
julius
julius3y ago
no, and it's probably not linkAccount you want to override. what i do is i check the source of the adapter and modify the function slightly. you don't need to call it somewhere - nextauth will do that automatically https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-prisma/src/index.ts i'd imagine it's the getUser you want to override
const authOptions: NextAuthOptions = {
adapter: {
...PrismaAdapter(prisma),
async getUser(id) {
return await prisma.user.findUnique({
where: { id },
include: { teacher: true },
});
},
},
...
};
const authOptions: NextAuthOptions = {
adapter: {
...PrismaAdapter(prisma),
async getUser(id) {
return await prisma.user.findUnique({
where: { id },
include: { teacher: true },
});
},
},
...
};
@cje should we include a snippet like this in the docs?
nexxel
nexxel3y ago
i think it would be helpful
julius
julius3y ago
fyi: didn't @ you cause you usually don't like that
cje
cje3y ago
ive never seen/used this pattern before, my instinctive response is to keep the session object as small as possible even if it means more queries, but if a lot of people want to do this sort of thing then it might be helpful to put this in the docs we already have a bunch of other snippets so sure
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
julius
julius3y ago
this isn't about the session only though. it's more if you want additional fields on your account/user etc
nexxel
nexxel3y ago
oh i don't mind haha yeah its tough, most of the time you will have to write that code yourself unfortunately we don't have a better library for auth in nextjs next-auth is great for "oh i need auth in my project" but it's flaws come to be seen as soon as you want to so something custom and a bit complicated
kacakthegreat
kacakthegreatOP3y ago
I think doing this approach will get unexpected behavior
const authOptions: NextAuthOptions = {
adapter: {
...PrismaAdapter(prisma),
async getUser(id) {
return await prisma.user.findUnique({
where: { id },
include: { teacher: true },
});
},
},
...
};
const authOptions: NextAuthOptions = {
adapter: {
...PrismaAdapter(prisma),
async getUser(id) {
return await prisma.user.findUnique({
where: { id },
include: { teacher: true },
});
},
},
...
};
@julius
julius
julius3y ago
Why?
kacakthegreat
kacakthegreatOP3y ago
Type '(User & { teacher: Teacher | null; }) | null' is not assignable to type 'AdapterUser | null'.
Type '(User & { teacher: Teacher | null; }) | null' is not assignable to type 'AdapterUser | null'.
julius
julius3y ago
You have to do module augmentation too So the types match
kacakthegreat
kacakthegreatOP3y ago
createUser: (data) => p.user.create({ data }),
7 getUser: (id) => p.user.findUnique({ where: { id } }),
→ 8 getUserByEmail: (email) => p.user.findUnique(
The column `User.teacherId`
does not exist in the current database. Error:
Invalid `p.user.findUnique()` invocation in
createUser: (data) => p.user.create({ data }),
7 getUser: (id) => p.user.findUnique({ where: { id } }),
→ 8 getUserByEmail: (email) => p.user.findUnique(
The column `User.teacherId`
does not exist in the current database. Error:
Invalid `p.user.findUnique()` invocation in
under the node_modules itself?
julius
julius3y ago
types/next-auth.d.ts something like this:
// [...nextAuth].ts
adapter: {
...PrismaAdapter(prisma),
async getUser(id) {
return (await prisma.user.findUnique({
where: { id },
include: { randomModel: true },
})) as unknown as AdapterUser;
},
},
callbacks: {
session({ session, user }) {
if (session.user) {
session.user.id = user.id;
session.user.randomModel = user.randomModel;
}
return session;
},
},
// [...nextAuth].ts
adapter: {
...PrismaAdapter(prisma),
async getUser(id) {
return (await prisma.user.findUnique({
where: { id },
include: { randomModel: true },
})) as unknown as AdapterUser;
},
},
callbacks: {
session({ session, user }) {
if (session.user) {
session.user.id = user.id;
session.user.randomModel = user.randomModel;
}
return session;
},
},
// types/next-auth.d.ts
import { type RandomModel } from "@prisma/client";
import { type DefaultSession, type User as $User } from "next-auth";

declare module "next-auth" {
interface Session {
user?: {
id: string;
randomModel: RandomModel;
} & DefaultSession["user"];
}
interface User extends $User {
randomModel: RandomModel;
}
}
// types/next-auth.d.ts
import { type RandomModel } from "@prisma/client";
import { type DefaultSession, type User as $User } from "next-auth";

declare module "next-auth" {
interface Session {
user?: {
id: string;
randomModel: RandomModel;
} & DefaultSession["user"];
}
interface User extends $User {
randomModel: RandomModel;
}
}
kacakthegreat
kacakthegreatOP3y ago
Thanks man for helping!
USER {
id: 'clai6o4sg0000rpq16vyp05jo',
name: 'Learnpal',
emailVerified: null,
image: 'image',
createdAt: 2022-11-15T12:21:04.192Z,
updatedAt: 2022-11-15T12:21:04.192Z
}





SESSION {
user: {
name: 'Learnpal',
image: 'image'
},
expires: '2022-12-15T12:21:04.245Z'
}
USER {
id: 'clai6o4sg0000rpq16vyp05jo',
name: 'Learnpal',
emailVerified: null,
image: 'image',
createdAt: 2022-11-15T12:21:04.192Z,
updatedAt: 2022-11-15T12:21:04.192Z
}





SESSION {
user: {
name: 'Learnpal',
image: 'image'
},
expires: '2022-12-15T12:21:04.245Z'
}
Unfortunately the teacher attribute is not there and im not sure why I think getUser is not getting triggered
async getUser(id: string) {
console.log("GET USER ID", id);
return (await prisma.user.findUnique({
where: { id },
include: { teacher: true },
})) as unknown as AdapterUser;
},
async getUser(id: string) {
console.log("GET USER ID", id);
return (await prisma.user.findUnique({
where: { id },
include: { teacher: true },
})) as unknown as AdapterUser;
},
most likely when i login its calling this function
getSessionAndUser
getSessionAndUser
@julius
julius
julius3y ago
https://github.com/nextauthjs/next-auth/blob/8387c78e3fef13350d8a8c6102caeeb05c70a650/packages/adapter-prisma/src/index.ts#L24 the principle is the same, i dont know exactly which method you need to override, maybe this one include the teacher in that query:
async getSessionAndUser(sessionToken) {
const userAndSession = await p.session.findUnique({
where: { sessionToken },
include: { user: { include: { teacher: true } } },
})
if (!userAndSession) return null
const { user, ...session } = userAndSession
return { user, session }
},
async getSessionAndUser(sessionToken) {
const userAndSession = await p.session.findUnique({
where: { sessionToken },
include: { user: { include: { teacher: true } } },
})
if (!userAndSession) return null
const { user, ...session } = userAndSession
return { user, session }
},
or do this if you feel more comfortable with that. it will trigger 1 extra db call but that's not the end of the world
kacakthegreat
kacakthegreatOP3y ago
hahaha you are right Thanks btw for helping 🙇‍♂️ @julius is there any chance i can donate to you? the least i could do to contribute
julius
julius3y ago
im on github sponsors but don't worry about it. happy to help https://github.com/sponsors/juliusmarminge
GitHub
Sponsor @juliusmarminge on GitHub Sponsors
Support juliusmarminge's open source work
Want results from more Discord servers?
Add your server