Replies: 12 comments 26 replies
-
I was also struggling with this issue just now, and having a solution built in would be great. That being said, the workaround you mentioned can be simplified a bit with error message and a simple wrapper for client side. In callback I get a user from db, and check if he is Active. If not, I would attatch error message to the session object. async session(props) {
const user = await getUser()
if (user?.isActive === false) {
props.session.error = "inactive-user"
}
props.session.token = props.token
return props.session;
}, In client side I create a wrapper, that we can put around secure routes to signOut user if error is available in session object: import { signOut, useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";
type Props = {
children: React.ReactNode
}
export const Auth = ({ children }: Props) => {
const { data: sessionData } = useSession();
const router = useRouter();
useEffect(() => {
if (sessionData?.error === "inactive-user") {
// Sign out here
signOut();
}
}, [sessionData?.error, router]);
return (
<>{children}</>
)
} Not ideal, but at least a simplification. Hope it helps someone! |
Beta Was this translation helpful? Give feedback.
-
This is the exact question I have though I think of deleting next-auth.session-token |
Beta Was this translation helpful? Give feedback.
-
Next-Auth Session-token issues have been hindering me for half month.
|
Beta Was this translation helpful? Give feedback.
-
I'm facing a similar issue with the
|
Beta Was this translation helpful? Give feedback.
-
Would love to see a better solution for this and have similar issues. Forcing people to use JWT for CredentialsProvider is one thing, but not being able to kick out those users (eg if they are bad actors) easily is really another. Is there any movement at all to allowing CredentialsProvider to be used with database strategy or is that a dead end? There is a workaround to this here that apparently will let you store the ProviderCredentials in the session in the DB, but its super complicated and veers quite far away from the core functionality of NextAuth which isn't optimal. |
Beta Was this translation helpful? Give feedback.
-
@EvHaus in my case, i used nextjs 13 (pages)... i found the solution, at least for now. first create /api/auth/me endpoint import { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth";
import { authOption } from "./[...nextauth]";
import { db } from "@/server/db";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === "GET") {
// you can use getToken()
const session = await getServerSession(req, res, authOption);
if (!session) {
return res.status(401).json({ code: "UNAUTHORIZED" });
}
const user = await db.user.findUnique({
where: {
// I initialize the id as a sub on the session token
id: session?.user?.sub
},
});
if (!user) {
return res.status(401).json({ code: "UNAUTHORIZED" });
}
res.status(200).json({ code: "OK", data: { ...user } });
}
} after that check the end-point /api/auth/me using nextjs middleware import { NextResponse, type NextRequest } from "next/server";
// check if user is authorized
const getUser = async (req: NextRequest) => {
const user = await fetch("http://localhost:3000/api/auth/me", {
headers: {
Cookie: req.cookies as any,
},
}).then((res) => res.json());
return user;
};
export default async function middleware(request: NextRequest) {
const user = await getUser(request);
const path = request.nextUrl.pathname;
// reject unauthorized user
if (user.code === "UNAUTHORIZED" && path.includes("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
if (user.code === "OK" && path === "/login") {
return NextResponse.redirect(new URL("/dashboard", request.url));
}
}
// middleware only run on specific paths
export const config = {
matcher: ["/login", "/dashboard/:path*"],
}; every time the status code is 401 ("UNAUTHORIZED"), the server automatically deletes the next-auth.session-token cookie. |
Beta Was this translation helpful? Give feedback.
-
Sorry for the delay everyone, with works and the 1 million notifications on 100 different platforms github notifications goes unnoticed. NOTE:: I am not longer using the nextAuth wrapper in the auth config. Some tech info:
DB setup:
Resources:
async redirect({ baseUrl }) {
return `${baseUrl}/home`
},
async session({ session, user }) {
const completeUser: User = user as unknown as User
return {
...session,
user: {
...completeUser,
},
}
},
},
import { NextApiRequest, NextApiResponse } from 'next'
import { getServerSession } from 'next-auth'
import prismaExtended from '~/lib/prisma'
import { authOptions } from './[...nextauth]'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'GET') {
const session = await getServerSession(req, res, authOptions)
if (!session) {
return res.status(401).json({ code: 'UNAUTHORIZED' })
}
const userToUpdate = await prismaExtended.user.findUnique({
where: {
email: session?.user?.email || '',
},
select: { id: true, email: true, buyer: true },
cacheStrategy: { ttl: 60 },
})
const hasBuyer = await prismaExtended.buyer.findUnique({
where: { email: userToUpdate?.email as string },
})
if (hasBuyer) {
await prismaExtended.buyer.updateMany({
where: { email: hasBuyer.email, deleted: { not: null } },
data: { deleted: null, ...(userToUpdate?.id && { authId: userToUpdate.id }) },
})
if (userToUpdate?.buyer?.email !== hasBuyer.email) {
await prismaExtended.user.update({
where: { email: hasBuyer.email, buyerEmail: null },
data: { buyerEmail: hasBuyer.email },
})
}
}
const user = await prismaExtended.user.findUnique({
where: {
email: userToUpdate?.email || '',
},
select: { buyer: true },
cacheStrategy: { ttl: 60 },
})
if (!user) {
return res.status(401).json({ code: 'UNAUTHORIZED' })
}
res.status(200).json({ code: 'OK', data: { ...user } })
}
}
const getUser = async (req: NextRequest): Promise<UserApi> => {
const { data: user } = await fetch(`${req.nextUrl.origin}/api/auth/me`, {
headers: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Cookie: req.cookies as any,
},
}).then((res) => res.json())
return user
}
const middleware = async (request: NextRequest) => {
const user: UserApi = await getUser(request)
const token = request.cookies.get('next-auth.session-token')
const pathnameSegments = request.nextUrl.pathname.split('/')
const currentAppPath = getCurrentAppPath(pathnameSegments)
const currentAction = getCurrentAction(pathnameSegments)
// if the user is not on the home page AND there is no user object or token redirect the user to auth page.
if (currentAppPath !== AppPath.home && (!user || !token)) {
return NextResponse.redirect(new URL('/api/auth/signin', request.url))
}
// If the user is authed and on the home page AND has not created a user profile redirect the user to create a profile type.
if (
currentAppPath === AppPath.home && !user?.agent
? currentAppPath === AppPath.home && !user?.buyer
: currentAppPath === AppPath.home && !user?.agent
) {
return NextResponse.redirect(new URL('/select-profile-type', request.url))
}
if (currentAppPath && currentAction) {
const allowedRoles = PERMISSIONS[currentAppPath as AppPathType][currentAction as Actions]
const userType: UserType = user?.buyer?.type || user?.seller?.type
if (!allowedRoles.includes(userType)) {
return NextResponse.redirect(new URL('/', request.url))
}
}
return NextResponse.next()
}
export default middleware when user deletes account const handleDeleteUserAccount = async () => {
// TODO:: remove this I dont think I need it ??
// const signOutResponse = await signOut({ redirect: false, callbackUrl: `/${AppPath.home}` })
// console.log(
// '🚀 ~ file: index.tsx:248 ~ handleDeleteUserAccount ~ signOutResponse:',
// signOutResponse,
// )
// // P2025 DeleteSessionError
const response = await fetcher<unknown, { id: number | undefined }>(
`${endPoint}/delete-user-account/${userTypes[userType]}`,
{ id: data?.id },
'POST',
)
// if a date timestamp is returned the user has successfully been deleted.
if (response) {
router.push(`/${AppPath.home}`)
}
}
import { NextApiRequest, NextApiResponse } from 'next'
import { getServerSession } from 'next-auth/next'
import prismaExtended from '~/lib/prisma'
import { authOptions } from '../../auth/[...nextauth]'
const userDeleteHandler = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
const session = await getServerSession(req, res, authOptions)
if (session) {
const { body, query, method } = req
const userId = body.id
switch (method) {
case 'POST':
try {
if (query.userType === 'buyer') {
const data = await prismaExtended.buyer.delete({
where: {
id: userId,
},
})
await prismaExtended.user.delete({
where: {
id: data.authId,
},
})
const buyer = { buyer: data.deleted }
return res.status(200).json(buyer)
}
} catch (error: unknown) {
console.error('🚀 ~ file: index.ts ~ line 45 ~ error', error)
// throw error for errorWrapper to handle?
// send to some error logging service?
const commonError = error as unknown as object
return res.status(500).json({
error: {
...commonError,
message: '',
},
})
}
break
default: {
res.setHeader('Allow', ['OPTIONS'])
res.status(405).end(`Method ${method} Not Allowed`)
}
}
} else {
res.send({
error: 'You must be signed in to view the protected content on this page.',
})
}
}
export default userDeleteHandler In the end the user row is deleted from the database I am using At this stage I am still in development of this side project, I have not done much analysing of the code yet, so I have dumped some code here have a think about it ask me questions I will also have a think about it, and if any of this code is useful or my approach is useful for you please take it and run with it, I think if you have questions about the code please copy paste the code snippet and then ask your question. If you think this code is rubbish that is fair, I have no attachment to it do if you want to help me improve it please share. Cheers 🍻 P.S. if this code looks like gibberish let me know I will add some clarity to the parts you are interested in. |
Beta Was this translation helpful? Give feedback.
-
I expire the token instantly, I get an unauthorized or 401 error. I set the expiration date to now . That is the only strategy I'm currently using.
|
Beta Was this translation helpful? Give feedback.
-
is there any fix for this? |
Beta Was this translation helpful? Give feedback.
-
I'm using "next-auth": "^4.24.7", and if I throw an error in either the jwt or session callbacks, the token is removed on the client and the session status changes to "unauthenticated" |
Beta Was this translation helpful? Give feedback.
-
Next 14 here. I wanted to sign out on 401 here is my solution.
axios.interceptors.response.use(undefined, (error) => {
if (axios.isAxiosError(error)) {
if (error.response?.status === 401) {
redirect("/sign-out");
}
}
return Promise.reject(error);
});
"use client";
import { signOut } from "next-auth/react";
import { useEffect } from "react";
const SignOut = () => {
useEffect(() => {
void signOut();
}, []);
return null;
};
export default SignOut; I guess that could be also done with route handlers and cookie deletion. |
Beta Was this translation helpful? Give feedback.
-
Maybe this could be helpful to solve this issue #5334 (comment) |
Beta Was this translation helpful? Give feedback.
-
I am using
CredentialsProvider
and Prisma to store my users. Everything works great except for 1 problem. If I delete a user from my database (ie. I want to block their access to my app),next-auth
doesn't force their session to get logged out. When that user refreshes the page, it lets them get all the way through because it continues to use their JWT cookie token.Looking through the various
next-auth
issues, discussions & docs I feel like I'm stuck in an infinite loop.session.strategy = "database"
but when I set it I run intoCALLBACK_CREDENTIALS_JWT_ERROR
and the docs say I can't useCredentialsProvider
with the "database" strategy -- it must be JWT. So that's a dead end.I think my ideal solution would be to do something like this in the
session
callback where I'm already checking for the existence of a user:The only possible solution I have found so far is to manually do a check for
session.user
EVERYWHERE in my app where I calluseSession()
. This is what I'm doing to workaround this limitation, but man-oh-man -- this is really awful as it causes so much code duplication and is super risky in terms of security. What if I miss a spot...Is there any other possible solution here? What's the reason I can't use "database" strategy with CredentialsProvider? That seems like it would give me exactly the type of control that I need.
Beta Was this translation helpful? Give feedback.
All reactions