Closed Celsiusss closed 4 months ago
I've encountered the same issue, and it would be fantastic if it worked as described in the documentation. I aim to avoid exposing the access token to the client.
As far as I understand, there isn't a way to obtain the up-to-date JWT in server calls. Although there's a getToken method, it only retrieves the JWT from the request, which might require updating (e.g., refresh token rotation). Getting the updated JWT directly from auth() would resolve this.
Just ran into this as well after upgrading from a previous beta version. This is a pretty critical bug.
I also met the issue and figured out that the migration guide of this part was a little misleading. After tracing the source code, I found that the auth
returned by NextAuth
was just a getSession
call with auth config. According to the documentation, both of the returned values of auth
and getSession
are Session
and Session
is the return value of session()
callback, so the return value of auth
is the return value of session()
callback, not the return value of jwt()
callback. The return value of jwt()
callback will be encoded and set within server side cookie.
If we want auth
to contain the returned value of jwt()
callback, we can do this within session()
callback.
callbacks: {
session({ session, token }) {
// token is the returned value of `jwt()`
return { ...session, ...token }
// Can just return token if you want.
}
}
@judewang The problem with this approach is that the unencrypted token is exposed to the client.
@czymstef I supposed we can avoid exposing the token without using useSession
, was I wrong? Since we are always on the server, are there any differences that the Session
returned from auth
is somehow by session()
or by jwt()
?
BTW the encrypted token is within cookie.
As far as I know, there is still the /api/auth/session endpoint (used by useSession()
), which is just one GET-request away. I'm sure that there is a way to "disable" this endpoint (e.g. by defining a no op api route with the same name), but I'd not bet that there is no other way to somehow get the token in a compromised client.
I got a 404 error when trying to access /api/auth/session
.
The values within cookie were also encrypted.
So is there no way at the moment to get the raw token server-side? Seems pretty limiting
@judewang go to https://next-auth-example.vercel.app/, which should run on next-auth v5 beta. Login using github, then in the developer console write:
await fetch("https://next-auth-example.vercel.app/api/auth/session").then(res => res.json())
You should get the result from the session()
callback, which is where the token would be leaked if I'd include it in the session.
@alessandrojcm I think there is a way using getToken()
, but as far as I know it won't call the jwt() callback and thus does not run stuff like refresh token rotation.
@czymstef I tried https://next-auth-example.vercel.app and also got my token printed within the console. However when doing the same thing on my site, I still got a 404 error. I noticed that I didn't export the handlers
to any api routes, and that was what I found the demo page done, so maybe that caught the leak.
@judewang go to https://next-auth-example.vercel.app/, which should run on next-auth v5 beta. Login using github, then in the developer console write:
await fetch("https://next-auth-example.vercel.app/api/auth/session").then(res => res.json())
You should get the result from thesession()
callback, which is where the token would be leaked if I'd include it in the session.@alessandrojcm I think there is a way using
getToken()
, but as far as I know it won't call the jwt() callback and thus does not run stuff like refresh token rotation.
Yeah I noticed getToken
only decodes the token from the cookies, that may or not be enough depending on your use case; still I believe the library should offer a native way or getting the token as it was in v4. Alternatively one could export the function that does token rotation an call that beofore of getToken
, although that is not ideal.
As per adding the token to the session
callback, I think that could be done and, to avoid leaking the token, one could catcth the call to api/auth/session
within the route handler and just strip the token from the response. Although you'd need to be very careful where you call getSession
to avoid leaking the token.
@alessandrojcm Mind sharing how you could get the token server side in v4? I thought that this was not possible, too (with refresh token rotation and all).
@judewang If you don't export the handlers, then the callback endpoint does not exist, too, which makes logging in impossible, doesn't it? But we could only export the required handlers and just don't export the session one, or as @alessandrojcm suggested: Catch the call to /session and strip it. But who says that authjs won't add a method at a later stage which uses server actions?
This is just not as the library is intended to be used; session()
is for client side data. I don't think you should have to jump through those hoops to just get the (possibly refreshed, including new Set-Cookie headers) value from jwt()
on server side.
@alessandrojcm Mind sharing how you could get the token server side in v4? I thought that this was not possible, too (with refresh token rotation and all).
@judewang If you don't export the handlers, then the callback endpoint does not exist, too, which makes logging in impossible, doesn't it? But we could only export the required handlers and just don't export the session one, or as @alessandrojcm suggested: Catch the call to /session and strip it. But who says that authjs won't add a method at a later stage which uses server actions?
This is just not as the library is intended to be used;
session()
is for client side data. I don't think you should have to jump through those hoops to just get the (possibly refreshed, including new Set-Cookie headers) value fromjwt()
on server side.
There is v4's getToken, though now that I think of it I am not entirely sure if that does execute session
; you might be right.
And as per your second point, yes you are right one should not rely on patching session
since it might either break in the future or we might be leaking the token somewhere else. IMO there should be a way of getting the raw token server-side as we might need it, for examplet to authenticate with 3rd party APIs if necessary.
Just to add, I am not quite able to get the getToken
function from @auth/core/jwt
to work. Even tho I am passing the same parmeters to the function that get passed by the library internally; even is this worked I don't think it'd be a good idea to mimic the internals. Maybe @balazsorban44 or someone from the dev team could advice in this case on how to get the raw JWT token?
Just to emphasize this again: I personally do not only want the raw unencrypted token, but have a refreshed one. In other words: I'm looking for a method to get the content of the jwt()
callback including a Set-Cookie header in case the jwt()
content changed (e.g. refresh token rotation occured). Something like unstable_getServerSession()
, but for the jwt()
content instead of session()
If I understand the documentation correctly, getToken()
does indeed get the raw token, but it's the token from the cookie, which is potentially stale
I'm experiencing the same issue. Has there been any update or resolution?
I'm attempting to retrieve user IDs using auth()
in Server Components, but returned user
object includes only name
, email
, and image
.
const session = await auth();
console.log(session.user) // { image: '...', email: '...', name: '...' }
I ran through the code and figured out that this segment does nothing:
Because the supplied args[0].session
has hard-coded user
which only contains them.
@G4RDS I have the same issue, did you find any solution?
@G4RDS does doing the module augmentation had any positive impact on solving the issue? or did you find a solution?
If you need more info from the OIDC provider you can always get it by modifying the session object through the jwt
and session
callback. Just get whatever you need from the account
parameter that gets passed to the jwt
callback, then that object gets passed down to session
and whatever you return from that you can get through auth
. I do it in my app:
session: ({ session, token }) => {
// token has the object returned from the jwt callback
// here you can modify the session object as you please
session.user = {...session.user, id: token.user.id}
return session
},
jwt: async ({ account, user }) => {
// account has the user info from the OIDC provider
if (account) {
return {
user: {
...user,
// add whatever property from account
id: account.id
},
}
// @ts-expect-error
}
return user
}
},
You can then use module augmentation to get type completion.
Is any of you guys by any chance using the drizzle-adapter and have the extended session object working?
For me that's not true I doesn't have the jwt
properties in my session
object
Currently facing the same issue, does not feel safe including 3rd party token in the session object.
Just to add, I am not quite able to get the
getToken
function from@auth/core/jwt
to work. Even tho I am passing the same parmeters to the function that get passed by the library internally; even is this worked I don't think it'd be a good idea to mimic the internals. Maybe @balazsorban44 or someone from the dev team could advice in this case on how to get the raw JWT token?
I managed to get the getToken(...) to return the JWT with the following Route Handler:
Intentionally didn't send in a salt param, typescript does not approve.
But as @czymstef mentions, with this the token is extracted from the cookie, which makes the token potentially stale.
--- EDIT --- Managed to get the JWT from a Server Action as well. This is from a Next.js app.
import { getToken } from "@auth/core/jwt";
import { cookies } from 'next/headers'
export async function action() {
"use server"
const all_cookies = cookies().getAll();
const headers = new Headers();
all_cookies.forEach((cookie) => {
headers.set("cookie", `${cookie.name}=${cookie.value};`)
});
const req = {
headers
};
const secureCookie = process.env.NODE_ENV === "production";
const cookieName = secureCookie
? "__Secure-authjs.session-token"
: "authjs.session-token"
const jwt = await getToken({ req, secret: process.env.AUTH_SECRET!, secureCookie, salt: cookieName });
console.log(jwt);
}
whats is resolver ? 🚩
Hey, I'm facing the same issue. However, you can "access" the token
object even though there's a type error
async session({ session, token }) {
session.user.id = token.id;
return session;
},
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
Has anyone found a way to access the JWT token server side / api routes? Upgrading to v5 really seems like a down grade so far.
Has anyone found a way to access the JWT token server side / api routes? Upgrading to v5 really seems like a down grade so far.
Not tried a API route, but managed to access the JWT via getToken(...) in a Next.js Route Handler and a Server Action (https://github.com/nextauthjs/next-auth/issues/9122#issuecomment-1898644981).
All it does is decode the cookie with your AUTH_SECRET.
It's a work around for sure but as long as you have the Request Headers available you should be able to use this method if thinking.
Hey, I'm facing the same issue. However, you can "access" the
token
object even though there's a type errorasync session({ session, token }) { session.user.id = token.id; return session; }, async jwt({ token, user }) { if (user) { token.id = user.id; } return token; },
Just as a note, the type error with using token in the session callback is due to the typing where it will either have a token (if using jwt) or a user (if using a database); you can get around the type error somewhat by using the in operator narrowing:
session(sessionArgs) {
// token only exists when the strategy is jwt and not database, so sessionArgs here will be { session, token }
// with a database strategy it would be { session, user }
if ("token" in sessionArgs) {
return {
...sessionArgs.session,
user: {
...sessionArgs.session.user,
roles: sessionArgs.token.roles, // something you are bringing in from your hwt function return via the token
},
};
}
return sessionArgs.session;
}
You can also augment the type declaration to get rid of the type error in a cleaner way.
Yes good point! I was augmenting my session shape to avoid errors, didn't think of augmenting the session function args too.
How did you augment your session shape to avoid errors @xiavn , can you provide the code please?
@vineetkia You might find this page helpful buried deep within the docs - Module Augmentation. Essentially you declare a module for 'next-auth' (or any other module you might want to override and then redeclare the interface's with new properties, and then anywhere in your code that you use somethign that has Session, it will have all the new properties you are planning to include, so you don't have to fight typescript.
declare module "next-auth" {
interface Session {
address: string
}
}
It will merge in new properties. If you want to modify a nested object within you need to tweak slightly:
declare module "next-auth" {
interface Session {
user: {
address: string
} & DefaultSession["user"]
}
}
You can just inport DefaultSession
from the next-auth library.
Thanks to everyone for their help. For some reason I had to add more type checks to make it work, in case anyone wants a fully working auth.config.ts example ...
import type { NextAuthConfig } from 'next-auth';
import { DefaultSession } from 'next-auth';
import { User as NextUser } from 'next-auth';
declare module "next-auth" {
interface Session {
user: {
id: string
} & DefaultSession["user"]
}
}
export const authConfig = {
pages: {
signIn: '/login',
},
session: {
strategy: "jwt",
},
callbacks: {
authorized({ auth, request: { nextUrl } }: { auth: any, request: { nextUrl: any } }) {
// fill in the blank here
},
jwt({ token, user }) {
if (user) token.user = user;
return token;
},
session(sessionArgs) {
// token only exists when the strategy is jwt and not database, so sessionArgs here will be { session, token }
// with a database strategy it would be { session, user }
if ("token" in sessionArgs) {
let session = sessionArgs.session;
if ("user" in sessionArgs.token) {
const tokenUser = sessionArgs.token.user as NextUser;
if (tokenUser.id) {
session.user.id = tokenUser.id;
return session;
}
}
}
return sessionArgs.session;
},
},
providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;
I switched back to "5.0.0-beta.4" @lukasb , and it worked fine for me. in 5.0.0-beta.5 they had this breaking change. I guess until they go under stable release the 5.0.0-beta.4 works fine for session({ session, token })
The documentation has changed. The bit that I originally quoted is now gone. (b5d5f20)
The commit references another issue (#9329) that was created after this describing the same problem.
Quoting a comment by @balazsorban44 made on that issue:
we reverted the ignoring part and will rather introduce the change via a flag in https://github.com/nextauthjs/next-auth/pull/9702 so the behavior is more consistent/less breaking for now. Will update the migration guide shortly
https://github.com/nextauthjs/next-auth/issues/9329#issuecomment-1909255054
Seems like auth()
will only use the session()
callback. A new way of suing auth()
with AuthData
is in the works, and may solve this.
Would have appreciated some better communication seeing as this issue has gathered a lot of traction, but at the same time many people commenting here is not even commenting about the original issue that I described, so I can see due to the discussions that emerged here it may not be clear what this issue really was about.
currently migrating to v5. The guides are EXTREMELY useless. There are NO useable examples. I'm also pretty sure not everyone knows how to interface in order to achieve a custom dataset.
almost ranted... anyways, bottom line. The documentation is garbage and I've spent 2 days migrating something that should have taken hours.
Examples folks, ones that work. Ones that are current. P.S. Migration guide indicates use of middleware, your API says its deprecated and using it creates errors with bcrypt.(another issue)
sorry, nothing nice to say here, I guess I did rant..
Faced the same issue and the struggles with documentation about this use case. For me the only workable solution was to use getToken(), even though the docs say it is not recommended.
Environment
Reproduction URL
https://github.com/Celsiusss/nextauth-demo
Describe the issue
In the documentation, it says that:
I tried to test this, but could not get values from the
jwt()
callback to be returned byauth()
. It seems like the opposite of what this text says is true. Values from thesession()
callback is being returned, instead ofjwt()
. Not quite sure what the intended behavior really is, but I would love to access encrypted token values usingauth()
.How to reproduce
I have tried to create a minimal example in the Github link.
Here I am using
auth()
in a server component and in a route handler, to demonstrate it in two different ways.page.tsx
shows it being used in a server component.api/route.ts
shows it being used in a route handler.client.tsx
client component for signing in and using the route handlerauth.ts
registers an oidc provider and defines thejwt()
callback.Expected behavior
I expected to be able to use
auth()
to retrieve values from thejwt()
callback in server side components.