Closed cexra closed 10 months ago
@cexra Try using @auth/core/types
rather than @auth/core
, that worked for me.
yes the solution works but only partially, the solution solves the problem on jwt and session callback. but why user.name
is still a string | null | undefined
when I think user.name
should be a string
only. can anyone help me how to augment this type
import "next-auth";
declare module "next-auth" {
interface User {
role: "SUPER" | "USER";
}
interface Session {
user?: User;
}
}
declare module "@auth/core/jwt" {
interface JWT {
role: "SUPER" | "USER";
}
}
This works for me.
I tried both ways, and i run into the same issue:
Property 'id' does not exist on type '{ name?: string | null | undefined; email?: string | null | undefined; image?: string | null | undefined; }'.
import NextAuth from "next-auth";
import Discord from "next-auth/providers/discord";
import type { NextAuthConfig } from "next-auth";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { db } from "@/app/db";
import { pgTable } from "./db/schema";
import { env } from "@/env.mjs";
enum UserRole {
Admin = "admin",
User = "user",
Free = "free",
}
declare module "@auth/core/types" {
interface Session {
user: {
id: string;
// ...other properties
role: UserRole;
} & DefaultSession["user"];
}
interface User {
// ...other properties
role: UserRole;
}
}
export const config = {
theme: {
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
},
providers: [
Discord({
clientId: env.DISCORD_CLIENT_ID,
clientSecret: env.DISCORD_CLIENT_SECRET,
}),
],
adapter: DrizzleAdapter(db, pgTable),
callbacks: {
session: ({ session, user }) => ({
...session,
user: {
...session.user,
id: user.id,
role:session.user?.role
},
}),
},
} satisfies NextAuthConfig;
export const { handlers, auth } = NextAuth(config);
Have you tried to put your augmented types into a separate .d.ts
file?
I needed to update my package to next-auth": "5.0.0-beta.4
and drizzle-adapter:"^0.3.9"
, and could just run it inline.
Thanks all for confirming it was me and not NextAuth.
import NextAuth from "next-auth";
import Discord from "next-auth/providers/discord";
import type { NextAuthConfig } from "next-auth";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { db } from "@/app/db";
import { pgTable } from "./db/schema";
import { env } from "@/env.mjs";
enum UserRole {
Admin = "admin",
User = "user",
Free = "free",
}
declare module "@auth/core/types" {
interface Session {
user: {
id: string;
// ...other properties
role: UserRole;
}
}
interface User {
// ...other properties
role: UserRole;
}
}
export const config = {
theme: {
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
},
providers: [
Discord({
clientId: env.DISCORD_CLIENT_ID,
clientSecret: env.DISCORD_CLIENT_SECRET,
}),
],
adapter: DrizzleAdapter(db, pgTable),
callbacks: {
session: ({ session, user }) => ({
...session,
user: {
...session.user,
id: user.id,
role: session.user?.role,
},
}),
},
} satisfies NextAuthConfig;
export const { handlers, auth } = NextAuth(config);
@cexra Try using
@auth/core/types
rather than@auth/core
, that worked for me.
thanks bro
I gave this a go today and no luck still unfortunately. How come this works for you guys when this is still open? https://github.com/nextauthjs/next-auth/pull/8561
So I spend quite some time debugging this, as both @auth/core
& @auth/core/types
doesn't work for me, and I don't think adding the /types
is really necessary.
I managed to solve it by making the user
optional, which make sense as the user can be undefined when user is not login.
declare module "next-auth" {
interface Session {
user?: {
id: string;
// ...other properties
role: UserRole;
}
}
}
Notice that it also works with next-auth
in my case, so I believe it should also works with @auth/core
& @auth/core/types
.
I've tested various releases of next-auth@beta
, however, I am still unable to overwrite the default values in the User
interface. I saved the following into next-auth.d.ts
, however, I am still getting an error when I try to assign a Number
to id
in authorize()
.
declare module "next-auth" {
interface User {
id: number;
}
interface Session {
user?: User;
}
}
declare module "@auth/core" {
interface User {
id: number;
}
}
declare module "@auth/core/types" {
interface User {
id: number;
}
}
@rvndev did you include your next-auth.d.ts
in your tsconfig.json
types?
I've added this snippet in my auth.config.ts
and it seemed to work:
declare module "next-auth" {
interface Session {
user: {
address: string
} & User
}
interface User {
foo?: string
}
}
Check this out for more details: https://authjs.dev/getting-started/typescript
Extending the native Auth.js type + actually passing those extended values to the session is way more of a nightmare than it should be. Crazy this wasn't improved from v4 to v5, in fact, it's actually worse. Why is this issue closed?
Following the documentation perfectly on the TypeScript section throws errors and fails.
types.d.ts
import NextAuth, { type DefaultSession } from "next-auth"
declare module "next-auth" {
/**
* Returned by `auth`, `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user: {
/** The user's postal address. */
address: string
/**
* By default, TypeScript merges new interface properties and overwrites existing ones.
* In this case, the default session user properties will be overwritten,
* with the new ones defined above. To keep the default session user properties,
* you need to add them back into the newly declared interface.
*/
} & DefaultSession["user"]
}
}
auth.ts
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [redacted],
session: {
strategy: "jwt",
maxAge: 48 * 60 * 60, // 48 hours
},
callbacks: {
session({ session, token, user }) {
// `session.user.address` is now a valid property, and will be type-checked
// in places like `useSession().data.user` or `auth().user`
return {
...session,
user: {
...session.user,
address: user.address,
},
};
},
},
});
The above example is taken directly from the documentation, and yields:
Unsafe assignment of an error typed value.eslint[@typescript-eslint/no-unsafe-assignment](https://typescript-eslint.io/rules/no-unsafe-assignment)
Property 'address' does not exist on type 'AdapterUser'.ts(2339)
This issue needs to be re-opened, at a minimum the docs need to be fixed.
Following the documentation perfectly on the TypeScript section throws errors and fails.
types.d.ts
import NextAuth, { type DefaultSession } from "next-auth" declare module "next-auth" { /** * Returned by `auth`, `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context */ interface Session { user: { /** The user's postal address. */ address: string /** * By default, TypeScript merges new interface properties and overwrites existing ones. * In this case, the default session user properties will be overwritten, * with the new ones defined above. To keep the default session user properties, * you need to add them back into the newly declared interface. */ } & DefaultSession["user"] } }
auth.ts
export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [redacted], session: { strategy: "jwt", maxAge: 48 * 60 * 60, // 48 hours }, callbacks: { session({ session, token, user }) { // `session.user.address` is now a valid property, and will be type-checked // in places like `useSession().data.user` or `auth().user` return { ...session, user: { ...session.user, address: user.address, }, }; }, }, });
The above example is taken directly from the documentation, and yields:
Unsafe assignment of an error typed value.eslint[@typescript-eslint/no-unsafe-assignment](https://typescript-eslint.io/rules/no-unsafe-assignment) Property 'address' does not exist on type 'AdapterUser'.ts(2339)
This issue needs to be re-opened, at a minimum the docs need to be fixed.
@dir To fix this just augment the module "@auth/core/adapters".
You do need to install it though (pnpm add "@auth/core" and make sure it's the latest version by checking the npm releases or on github.)
Bellow see an example:
declare module "next-auth" {
interface Session {
user: {
role: UserRoles;
id: string;
email: string;
activated: UserActivated;
} & DefaultSession["user"];
}
interface User {
role: UserRoles;
activated: UserActivated;
}
}
//For the adaptor user objcet
//Here add any other fields you have in user table that are not part of the default schema.
declare module "@auth/core/adapters" {
interface AdapterUser extends User {
role: UserRoles;
activated: UserActivated;
}
}
@ypanagidis I appreciate your help! I installed @auth/core
at the latest version (0.34.1)
But... (after restarting my editor, clearing my modules folder, reinstalling, etc), I get
Invalid module name in augmentation, module '@auth/core/adapters' cannot be found.ts(2664)
⚠ Error (TS2664) |
Invalid module name in augmentation, module
cannot be found.
On a simple reproduction of your example:
declare module "@auth/core/adapters" {
interface AdapterUser extends User {
role: UserRoles;
activated: UserActivated;
}
}
This is nightmarish just to extend my session and add a couple of types. What a headache.
@ypanagidis I appreciate your help! I installed
@auth/core
at the latest version (0.34.1)But... (after restarting my editor, clearing my modules folder, reinstalling, etc), I get
Invalid module name in augmentation, module '@auth/core/adapters' cannot be found.ts(2664) ⚠ Error (TS2664) | Invalid module name in augmentation, module cannot be found.
On a simple reproduction of your example:
declare module "@auth/core/adapters" { interface AdapterUser extends User { role: UserRoles; activated: UserActivated; } }
This is nightmarish just to extend my session and add a couple of types. What a headache.
@dir I had the same issue in a monorepo, make sure to import anything from '@auth/core" and the error should go away! I think it's getting treeshaken
I tried everything above and I still couldn't overwrite the interfaces User and Session, any other suggestions?
package.json
{
"name": "application",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@emotion/is-prop-valid": "^1.2.2",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@next/third-parties": "^14.2.4",
"@react-oauth/google": "^0.12.1",
"@svgr/webpack": "^8.1.0",
"axios": "^1.7.2",
"date-fns": "^3.6.0",
"dotenv": "^16.4.5",
"firebase": "^10.12.3",
"next": "^14.2.4",
"next-auth": "^5.0.0-beta.20",
"react": "^18",
"react-apple-signin-auth": "^1.1.0",
"react-datepicker": "^7.2.0",
"react-dom": "^18",
"react-icons": "^5.2.1",
"react-input-mask": "3.0.0-alpha.2",
"styled-components": "^6.1.11",
"superstruct": "^2.0.2",
"unstorage": "^1.10.2"
},
"devDependencies": {
"@types/next-auth": "^3.15.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-input-mask": "3.0.2",
"@types/styled-components": "^5.1.34",
"eslint": "^8",
"eslint-config-next": "14.2.4",
"typescript": "^5"
}
}
Can someone explain why when setting different types in next-auth.d.ts
, I have all other types fail altogether
The file has been added to tsconfig.json
Fixes it if I move it to auth.ts are all types, but this file will be very huge anyway. I don't want to shove any more types in there.
I downgraded to stable version 4.24.7
.
@ypanagidis I appreciate your help! I installed
@auth/core
at the latest version (0.34.1)But... (after restarting my editor, clearing my modules folder, reinstalling, etc), I get
Invalid module name in augmentation, module '@auth/core/adapters' cannot be found.ts(2664) ⚠ Error (TS2664) | Invalid module name in augmentation, module cannot be found.
On a simple reproduction of your example:
declare module "@auth/core/adapters" { interface AdapterUser extends User { role: UserRoles; activated: UserActivated; } }
This is nightmarish just to extend my session and add a couple of types. What a headache.
Have you come across a solution yet?
@ypanagidis I appreciate your help! I installed
@auth/core
at the latest version (0.34.1)But... (after restarting my editor, clearing my modules folder, reinstalling, etc), I get
Invalid module name in augmentation, module '@auth/core/adapters' cannot be found.ts(2664) ⚠ Error (TS2664) | Invalid module name in augmentation, module cannot be found.
On a simple reproduction of your example:
declare module "@auth/core/adapters" { interface AdapterUser extends User { role: UserRoles; activated: UserActivated; } }
This is nightmarish just to extend my session and add a couple of types. What a headache.
Have you come across a solution yet?
I think it's been tree shaken away. Just import it and export the type
Trying multiple different way to override the User types with 5.0.0-beta.20
below
import 'next-auth'
import '@auth/core/types'
import '@auth/core'
declare module '@auth/core' {
// trying to override type of these field from being undefined | null
interface User {
id: string;
name: string;
image: string;
email: string | null;
emailVerified: Date | null;
}
/**
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user?: User;
}
interface Account {
accessToken: string;
refreshToken: string;
}
}
declare module 'next-auth' {
// trying to override type of these field from being undefined | null
interface User {
id: string;
name: string;
image: string;
email: string | null;
emailVerified: Date | null;
}
/**
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user?: User;
}
interface Account {
accessToken: string;
refreshToken: string;
}
}
Neither solutions work. That farthest I made it was overriding the session user type. This beta is definitely not cross-compatible with version 4 when it comes to overriding the next-auth types. Still not sure why this issue is still closed.
The only solution is to downgrade to the stable v4
release until this gets patched or another solution arises.
for those of you who still have problems with augmentation, I have a project that successfully performs augmentation, you can see it https://github.com/prave-com/electra. As an illustration, I added role attributes to the session and user objects. You can see the project directly, and here I will provide some of the code for you to see and study, I hope this can help you.
as a reminder, I once read that we should only use the same config for all next auth which I saved in auth.config.ts
then we use the that config in the auth.ts
and middleware.ts
files
package.json
file
{
"name": "electra",
"version": "0.1.0",
"private": true,
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
},
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"format:check": "prettier --check --ignore-unknown .",
"format": "prettier --write --ignore-unknown . && prisma format",
"postinstall": "prisma generate"
},
"dependencies": {
"@auth/prisma-adapter": "^2.4.2",
"@emotion/cache": "^11.13.1",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@hookform/resolvers": "^3.9.0",
"@mui/icons-material": "^5.16.7",
"@mui/material": "^5.16.7",
"@mui/material-nextjs": "^5.16.6",
"@prisma/client": "^5.18.0",
"bcryptjs": "^2.4.3",
"next": "14.2.5",
"next-auth": "5.0.0-beta.20",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.53.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/bcryptjs": "^2.4.6",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"csv-parse": "^5.5.6",
"eslint": "^8",
"eslint-config-next": "14.2.5",
"postcss": "^8",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.6",
"prisma": "^5.18.0",
"tailwindcss": "^3.4.1",
"ts-node": "^10.9.2",
"typescript": "^5"
}
}
next-auth.d.ts
file
import { Role } from '@prisma/client'
import NextAuth, { DefaultSession } from 'next-auth'
import { JWT } from 'next-auth/jwt'
declare module 'next-auth' {
interface Session {
user: {
role: Role
} & DefaultSession['user']
}
}
declare module 'next-auth/jwt' {
interface JWT {
role: Role
}
}
auth.config.ts
file
import { getUserById } from '@/data/user'
import { getUserByEmail } from '@/data/user'
import db from '@/lib/db'
import { LoginSchema } from '@/schema/login'
import { PrismaAdapter } from '@auth/prisma-adapter'
import bcrypt from 'bcryptjs'
import type { NextAuthConfig } from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import Google from 'next-auth/providers/google'
export default {
providers: [
Google,
Credentials({
name: 'Credentials',
credentials: {
username: { label: 'Username', type: 'text' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
const validatedFields = LoginSchema.safeParse(credentials)
if (validatedFields.success) {
const { email, password } = validatedFields.data
const user = await getUserByEmail(email)
if (!user || !user.password) return null
const passwordMatch = await bcrypt.compare(password, user.password)
if (passwordMatch) return user
}
return null
},
}),
],
events: {
async linkAccount({ user }) {
await db.user.update({
where: { id: user.id },
data: { emailVerified: new Date() },
})
},
},
pages: {
signIn: '/signin',
},
callbacks: {
async session({ token, session }) {
if (token.sub && session.user) {
session.user.id = token.sub
}
if (token.role && session.user) {
session.user.role = token.role
}
return session
},
async jwt({ token }) {
if (!token.sub) return token
const existingUser = await getUserById(token.sub)
if (!existingUser) return token
token.role = existingUser.role
return token
},
},
adapter: PrismaAdapter(db),
session: { strategy: 'jwt' },
} satisfies NextAuthConfig
auth.ts
file
import authConfig from '@/auth.config'
import NextAuth from 'next-auth'
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth(authConfig)
middleware.ts
file
import authConfig from '@/auth.config'
import {
administratorRoutePrefix,
apiAuthPrefix,
authRoutes,
operatorRoutePrefix,
publicRoutes,
} from '@/routes'
import { Role } from '@prisma/client'
import NextAuth from 'next-auth'
const { auth } = NextAuth(authConfig)
export default auth((req) => {
const { nextUrl } = req
const isLoggedIn = !!req.auth
const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix)
const isPublicRoute = publicRoutes.includes(nextUrl.pathname)
const isAuthRoute = authRoutes.includes(nextUrl.pathname)
const isAdministratorRoute = nextUrl.pathname.startsWith(
administratorRoutePrefix,
)
const isOperatorRoute = nextUrl.pathname.startsWith(operatorRoutePrefix)
const isAdministrator = req.auth?.user.role === Role.ADMINISTRATOR
const isOperator = req.auth?.user.role === Role.OPERATOR
const isGuest = req.auth?.user.role === Role.GUEST
if (isApiAuthRoute) return
if (isAuthRoute) {
if (isLoggedIn && isAdministrator) {
return Response.redirect(new URL(administratorRoutePrefix, nextUrl))
}
if (isLoggedIn && isOperator) {
return Response.redirect(new URL(operatorRoutePrefix, nextUrl))
}
if (isLoggedIn && isGuest) {
return Response.redirect(new URL('/', nextUrl))
}
return
}
if (isLoggedIn) {
if (isAdministratorRoute && !isAdministrator) {
return Response.redirect(
new URL('/signin?message=Not Authorized', nextUrl),
)
}
if (isOperatorRoute && !isOperator) {
return Response.redirect(
new URL(`/signin?message=Not Authorized`, nextUrl),
)
}
return
}
if (!isLoggedIn && !isPublicRoute) {
let callbackUrl = nextUrl.pathname
if (nextUrl.search) callbackUrl += nextUrl.search
const encodedCallbackUrl = encodeURIComponent(callbackUrl)
return Response.redirect(
new URL(`/signin?callbackUrl=${encodedCallbackUrl}`, nextUrl),
)
}
return
})
export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/(api|trpc)(.*)'],
}
@vexra thanks for chiming in with another example!
I just want to clarify one thing - about the auth.config.ts
and middleware split.
This was an old workaround required because many database adapters didn't work in edge runtimes yet (i.e. middlware).
So if you're not using a database, you don't need to do this split at all. In addition, many DB pkgs have been updated over the last months / year, for example prisma, and it may not be necessary when using prisma anymore either. Please check out our prisma docs here: https://authjs.dev/getting-started/adapters/prisma#edge-compatibility
I have issue making user.id
number
- change it from string
defined in AdapterUser
What is the improvement or update you wish to see?
next auth js provides documentation that does not work to augment the User interface in the
@auth/core
module.Is there any context that might help us understand?
from the existing documentation we can augment the User interface like the method below
next-auth.d.ts
augment Session interface and JWT interface it works. but augment User interface does not work. my purpose of augmenting the User interface is to persist the role attribute on the User object, so that I can store it on the Session object.
auth.config.ts
Does the docs page already exist? Please link to it.
https://authjs.dev/getting-started/typescript?frameworks=next