Implementing numeric user id with next-auth is a straightforward process that involves setting up

Recently, I came across create-t3-app and decided to try it out for my NextJS projects. It handles a lot of the initial setup work for TypeScript, trpc, prisma, and next-auth, which would save me a significant amount of time. While this is beneficial, it doesn't seem to be at the root of the issue I'm facing. The problem arises from using a MySQL database with auto-incrementing user IDs. The types defined in the next-auth package specify user IDs as strings (DefaultUser in next-auth/core/types.d.ts and AdapterUser in next-auth/adapters.d.ts both indicate the ID type as string despite comments mentioning UUIDs). To try and accommodate numeric user IDs, I attempted to extend the existing types by adding the following code snippet to next-auth.d.ts:

import { DefaultSession, DefaultUser } from 'next-auth'

declare module 'next-auth' {
  interface User extends DefaultUser {
    id: number;
  }
  interface Session {
    user?: {
      id: number;
    } & DefaultSession['user'];
  }
}

This modification seemed to work in most cases except in [...nextauth].ts where an error occurred:

Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'.ts(2322)

The error specifically points to the line session.user.id = user.id within this part of the code:

export const authOptions: NextAuthOptions = {
  // Include user.id on session
  callbacks: {
    session({ session, user }) {
      if (session.user) {
        session.user.id = user.id
      }
      return session
    }
  },
  adapter: PrismaAdapter(prisma),
  providers: []
}

export default NextAuth(authOptions)

I managed to resolve the TypeScript error by removing the id: string; line from the AdapterUser section in next-auth/adapters.d.ts, returning it to id: number; as specified in the modified User interface:

export interface AdapterUser extends User {
    id: string; // <-- I removed this
    email: string;
    emailVerified: Date | null;
}

Although I believe that adjusting the library's types to facilitate numeric user IDs should not be necessary, I have exhausted all other solutions and have not found relevant information online. Should I reconsider using numeric IDs even though they align with my database structure? For context, here are the versions of the technologies I am currently utilizing:

next 12.3.1
next-auth 4.12.3
react 18.2.0
react-dom 18.2.0
superjson 1.9.1
zod 3.18.0
eslint 8.22.0
postcss 8.4.14
prettier 2.7.1
prisma 4.4.0
tailwindcss 3.1.6
typescrypt 4.8.0

Answer №1

Regrettably, it appears that altering or modifying the types within the library is not possible - even attempting to adjust NextAuthOptions proves futile as subsequent property definitions must maintain the same type. For now, you will need to resort to a runtime check/coercion or casting:

export const authOptions: NextAuthOptions = {
  // Include user.id on session
  callbacks: {
    session({ session, user }) {
      if (session.user) {
        if (typeof user.id !== "number") throw new Error("id should be a number");
        session.user.id = user.id // OK
        // session.user.id = +user.id // riskier but still functional
        // session.user.id = user.id as number // also risky
      }
      return session
    }
  },
  adapter: PrismaAdapter(prisma),
  providers: []
}

Answer №2

Building upon the previous solution mentioned, I decided to enhance my workflow by implementing runtime typeguards.

In my specific scenario, my Prisma user model contained additional attributes:

model User {
  id                  Int                 @id @default(autoincrement())
  uuid                String              @unique @default(cuid())
  name                String?
  email               String?             @unique
  emailVerified       DateTime?
  image               String?
  accounts            Account[]
  categories          Category[]
  sessions            Session[]
  records             Record[]
  permissionsGranted  SharePermission[]     @relation("permissions_granted")
  permissionsReceived SharePermission[]     @relation("permissions_received")
  @@unique([id, uuid])
  @@index(fields: [id, uuid], name: "user_by_id_idx")
}

To handle these extra attributes, I proceeded to define the following typeguards:

import { User as AdapUser } from '@prisma/client';
import { Session, User } from 'next-auth';

export interface AuthenticatedSession extends Session {
    id: number;
    user: AdapUser;
}

export const isNextAuthUser = (value: unknown): value is User => {
    const { id, name, email, image } = (value ?? {}) as User;
    const idIsValid = typeof id === 'number';
    const nameIsValid = typeof name === 'string' || !name;
    const emailIsValid = typeof email === 'string' || !email;
    const imageIsValid = typeof image === 'string' || !image;
    return idIsValid && nameIsValid && emailIsValid && imageIsValid;
};

export const isAdapUser = (value: unknown): value is AdapUser => {
    const { uuid, emailVerified } = (value ?? {}) as AdapUser;
    const uuidIsValid = typeof uuid === 'string';
    const emailVerifiedIsEmail = emailVerified instanceof Date || !emailVerified;
    return isNextAuthUser(value) && uuidIsValid && emailVerifiedIsEmail;
};

export const isSession = (value: unknown): value is Session => {
    const { user, expires } = (value ?? {}) as Session;
    const userIsValid = isAdapUser(user) || isNextAuthUser(user) || !user;
    const expiresIsValid = typeof expires === 'string';
    return expiresIsValid && userIsValid;
};

export const isAuthenticatedSession = (value: unknown): value is AuthenticatedSession => {
    const session = (value ?? {}) as AuthenticatedSession;
    const sessionIsValid = isSession(session);
    const sessionUserIsValid = isAdapUser(session.user);
    return sessionIsValid && sessionUserIsValid;
};

Subsequently, within the session callback function, I applied one of the typeguards in this manner:

session: async ({ session, user }) => {
  if (!isAdapUser(user)) {
    return session;
  }
  const authenticatedSession: AuthenticatedSession = {
    ...session,
    user,
    id: user.id,
  };
  return authenticatedSession;
}

This adjustment results in the callback signature being Session | AuthenticatedSession, which I handled accordingly in subsequent sections.

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
    const session = await getSession({ req });
    if (!isAuthenticatedSession(session)) {
        return {
            props: {
                records: [],
            },
        };
    }
    const records = await prisma.record.findMany({
        where: {
            reporter: {
                id: session.id,
            },
        },
        orderBy: {
            date: 'asc',
        },
        include: {
            category: {
                select: {
                    title: true,
                },
            },
        },
    });
    return {
        props: { records },
    };
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Render JSON value as an input in an Angular component using @Input()

In my app.component.html file, I have included the following template: <test [someInput]="data"></test> The 'data' variable is a JSON object property structured like this: let data = {hello: "ciao"} Below is the code from my test. ...

"Experiencing sluggish performance with VSCode while using TypeScript and Styled Components

My experience with vscode's type-checking is frustratingly slow, especially when I am using styled components. I have tried searching for a solution multiple times, but have only come across similar issues on GitHub. I attempted to read and understa ...

The constant issue persists as the test continues to fail despite the component being unmounted from the

import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { act } from 'react' import Notifications, { defaultNotificationTime, defaultOpacity, queuedNotificationTime, fa ...

Utilizing next/image as a backgroundImage in a div container

Currently, I am working with nextjs and I am trying to set a background Image for a specific div using next/image. Most of the sources I found only explain how to implement full screen background images with next/image, not for a single div. I stumbled upo ...

Exploring Angular 6: Step-by-step guide to nesting objects within objects

I have a problem with posting data to my API built with Spring Boot. workflow : any = { name : "CMD1", items : [ { name : "work1", content : null }, { name : "work2", content : null } ] } In Angular, I created a ...

The error message "TypeError: (0 , N.createContext) is not a function" indicates that

I'm currently in the process of developing a cutting-edge application using Next.js 14, TypeScript, and Telegram Open Network. However, I've hit a roadblock while attempting to incorporate the TONConnectUIProvider at the root of my app. Upon run ...

The ID Token is required for local storage authentication. Please ensure you authenticate properly by signing in using the authenticator form

Currently working on setting up a basic authentication user interface with AWS Amplify 'use client' import { Authenticator } from "@aws-amplify/ui-react"; import '@/configureAmplify' export default function SignInPage() { ...

Utilize Firebase for Playwright to efficiently implement 'State Reuse' and 'Authentication Reuse'

In my testing environment, I want to eliminate the need for repeated login actions in each test run. My approach involves implementing 'Re-use state' and 'Re-use Authentication', but I've encountered a challenge with Firebase using ...

Upon initialization, Angular's BehaviorSubject is encountered as undefined

I am in the process of trying to create a fresh BehaviorSubject that is of the type ICar (which I have defined) in a service, with the initial value being either null or undefined. However, when I attempt to change the type to ICar | undefined, I encounter ...

Styling text with bold formatting can be done conditionally within JSX

Currently, I am developing a Next.JS Google Clone and facing an issue in styling the search input within the search results objects retrieved using the Google API. I want to find a way to dynamically bold the {router.query.term} within my {result.snippet} ...

Having trouble with using a .node addon in NextJS 14 (with Electron) - receiving a "module not found" error

I have recently developed a next.js application using version 14.2.2 I have also successfully created a nodejs addon by following this helpful tutorial: Now, I possess an addon.node file that I intend to utilize. My project structure looks li ...

Angular - The 'options' property is not found in the 'HTMLOptionElement' type

Currently, I am attempting to modify the choices in a dropdown menu based on the selection made from another dropdown. This problem led me to come across a helpful resource on a website called "Form Select Change Dynamic List Option Elements Tutorial". How ...

What is the best way to dynamically load application components based on incoming data?

I am working on an Angular project where I need to dynamically load different components based on API data. The home page consists of a banner and a news section. Instead of having the banner always at the top and the news section at the bottom, I want to ...

Having trouble locating an Ionic module with your Ionic Web Builder?

Currently, I am working on building my Ionic app using their web build system. In my app.component.ts file, I have included the following import statement: import { File } from '@ionic-native/File/ngx'; Although this project compiles successful ...

Guide on how to include jquery-ui css file in Vue3

I am new to TypeScript and VueJs. Recently, I decided to incorporate jquery-ui into my project using Vue3, TypeScript, and Electron. After some trial and error, I was able to successfully set up the environment. However, when I attempted to use the jquery ...

UnknownReferenceError: jwreload has not been declared (Exploring dynamic routing in next.js)

Error in dynamic route with next.js Recently, I started learning next.js and encountered an issue while implementing a dynamic route. The error message "ReferenceError: jwreload is not defined" keeps appearing whenever I reload the page. Surprisingly, des ...

Hide react component by clicking it

There is a cookies component with a button labeled "I agree" that I want to use to close the component when clicked. However, I am facing an issue in getting this functionality to work. I understand that the onClick event on the button should trigger an ...

Discovering the World of React with Typescript: Implementing Flexible Routes with BrowserRouter

When navigating to http://localhost:3000/confirm_email/, the route loads correctly. However, if I navigate to http://localhost:3000/confirm_email/h8s03kdbx73itls874yfhd where h8s03kdbx73itls874yfhd is unique for each user, I still want to load the /confirm ...

IDE flags an error with TypeScript type declarations

Here is how my type definition looks: export type AuthType = boolean | { roles: string[]; assistant?: string[] } | (() => void); Now, I need to check the type of the auth variable and assign a value or execute a function in this line of code: req.all ...

An issue has been detected in the jsoneditor component of the ang-jsoneditor module at line 13, character 9

Currently, I am facing an issue with my angular project when running npm start. Strangely enough, the problem only arises after doing a fresh npm install, not when copying over the older node-modules folder. There haven't been any recent changes to my ...