Prisma and NextAuth: Additional fields in the `User` model

I have a system where users have to enter some additional information such username or birthday, NextAuth says that they should be defined as nullabe fields in the schema, but I don't want to, is there a way around this?
21 Replies
gybia
gybia14mo ago
This. I tried recently to have required fields for user. This would mean that I would need to override user creation function. But I could not find a way. They do have a way to call a function AFTER a user has been created. But not before, or to override how user creation works. @Aerys could you please ping me in dm if you find a fix for this? As an example: here’s how I added a workspaceId to a user upon user creation. (See events.createUser function https://github.com/dBianchii/kodix-turbo/blob/main/packages/auth/index.ts)
Aerys
Aerys14mo ago
sorry for reaching you a bit late. in the end I just had to create a separate model and a one-to-one relation to the User model. so far so good.
gybia
gybia14mo ago
Humm, but did you make this extra model optional for the user? @Aerys This is a big issue from the next-auth package. There are many things that are wrong with it. This one is a big offender IMO
Aerys
Aerys14mo ago
it's just a reference from the new model to the User model, nothing needs to be optional so true
gybia
gybia14mo ago
@Aerys do you mind sharing the schema?
Aerys
Aerys14mo ago
No description
BigLung
BigLung14mo ago
I'm gonna be 100 we have a working version of this in our code base with a custom role property but I just tried to reproduce it from my works codebase on a personal project and It wasn't working so idk if its a bug, or if our cto did some witchcraft ill ask about it and let yall know lol
gybia
gybia14mo ago
How did you do it @BigLung Could you share?
BigLung
BigLung14mo ago
As I said I don't know how my team did it because I'm not the one who got it working when I tried to reproduce it on a new t3 app it didn't work though Waiting to see if someone did an extra config or something
Josh
Josh14mo ago
you can fully override the adapters pretty easily i personally found using a fully custom adapter was the best solution when you want a custom user table
Josh
Josh14mo ago
GitHub
t3-app-dir/src/server/auth.ts at main · GentikSolm/t3-app-dir
t3 app dir boilerplate. Contribute to GentikSolm/t3-app-dir development by creating an account on GitHub.
Josh
Josh14mo ago
in my example i didnt want nextauth to automatically fill the username field or whatever it was since i wanted to have the user choose that after they initially login if you look at the db schema file you can see i have a bunch of custom fields on there
gybia
gybia14mo ago
@Josh but I see you are not using notNull() in these custom user fields for your schema. This makes it so that the fields in user are optional. Which is not what we want. Yes?
Josh
Josh14mo ago
thats what i want but if you want them to be not null, just add notNull and then add them to the create statement
gybia
gybia14mo ago
I don’t think that works. I tried. It throws an error before reaching the createUser adapter
Josh
Josh14mo ago
"it throws error" does not help, what error did it throw, and what code did you use so that it was erroring What's most likely happening is that when you're not using a full custom adapter, the first thing it tries to do is fetch the user from the db to see if they exist. If you're using custom fields, this will fail unless you also overwrite your get user adapter. This is because under the hood next auth is using an orm that is trying to reference tables that do not exist. Orms will generally do some sort of "select table.column1, table.col2, ... etc" instead of "select *"
gybia
gybia14mo ago
I’ll try it again and report back Hi @Josh , thank you for sharing your repo. I managed to fix it! Here's what I did:
function CustomPrismaAdapter(p: typeof prisma): Adapter {
return {
...PrismaAdapter(p),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async createUser(data): Promise<any> {
const user = await p.user.create({
data: {
...data,
activeWorkspace: {
create: {
name: `${data.name ?? ""}'s Workspace`,
},
},
},
});

await p.workspace.update({
where: {
id: user.activeWorkspaceId,
},
data: {
users: {
connect: {
id: user.id,
},
},
},
});

return user;
},
};
}

export const {
handlers: { GET, POST },
auth,
CSRF_experimental,
} = NextAuth({
adapter: {
...CustomPrismaAdapter(prisma),
},
callbacks: {
async session({ session, user }) {
const activeWs = await prisma.workspace.findFirstOrThrow({
where: {
activeUsers: {
some: {
id: user.id,
},
},
},
select: {
name: true,
},
});
session.user.activeWorkspaceName = activeWs.name;
session.user.activeWorkspaceId = (user as User).activeWorkspaceId;

return session;
},
///...rest
});
function CustomPrismaAdapter(p: typeof prisma): Adapter {
return {
...PrismaAdapter(p),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async createUser(data): Promise<any> {
const user = await p.user.create({
data: {
...data,
activeWorkspace: {
create: {
name: `${data.name ?? ""}'s Workspace`,
},
},
},
});

await p.workspace.update({
where: {
id: user.activeWorkspaceId,
},
data: {
users: {
connect: {
id: user.id,
},
},
},
});

return user;
},
};
}

export const {
handlers: { GET, POST },
auth,
CSRF_experimental,
} = NextAuth({
adapter: {
...CustomPrismaAdapter(prisma),
},
callbacks: {
async session({ session, user }) {
const activeWs = await prisma.workspace.findFirstOrThrow({
where: {
activeUsers: {
some: {
id: user.id,
},
},
},
select: {
name: true,
},
});
session.user.activeWorkspaceName = activeWs.name;
session.user.activeWorkspaceId = (user as User).activeWorkspaceId;

return session;
},
///...rest
});
I created this CustomPrismaAdapter that returns Adapter and takes in my prisma object. I use this to override custom adapters. I had to use // eslint-disable-next-line @typescript-eslint/no-explicit-any in async createUser result.. but other than that, looks good!
Josh
Josh14mo ago
Iirc I had some errors too with eslint, the solution was going to nextauths source code and directly copying the entire adapter and then just changing what I needed Unironically it's actually way easier to see what's going on and change stuff with a fully custom adapter The one in my repo is my modification of the drizzle orm adapter
gybia
gybia14mo ago
Yes, it's very much more visible and can help you uderstand what is going on with Next-Auth behind the scenes I just feel like they should have streamlined this process way more for users. It's stupid that I have to go through this to adapt the adapter Or, at least have a section of it in the docs
Josh
Josh14mo ago
Agreed I may write a blog post on this actually
gybia
gybia14mo ago
Id like to see that @Josh
Want results from more Discord servers?
Add your server