Following-up on #325 because I'm not sure if GitHub sends you notifications after an issue has been closed.
I don't see a way to update data in the session after login. If I'm to store my tokens in the session, I need to be able to write to it after I refresh my access token. I've tried Googling for a solution, but to no avail. Is there something else I'm missing?
Thanks.
Yes if using JSON Web Tokens see the callback documentation, specifically the jwt() callback.
Alternatively, if using Database Sessions (and not JWT) you can add logic in the session() callback; but you will need to add logic required to write to the database yourself there.
There are a lot of closed issues related to this and they contain example code.
(We will probably have tutorials at some point).
@iaincollins, I'm not seeing how the jwt() callback is relevant. The doc says that the callback is invoked "whenever a JSON Web Token is created or updated" but it does not give any indication of how to initiate such a change. I get that this is called internally after a successful login--but I'm outside of that context when I need to update the tokens stored in the JWT.
Using a database session is not an option--all our communications with the database happen through a web service.
I did a search (e.g. "update jwt," "update session") through the closed issues for something that seemed relevant, but none of the issue titles gave strong clues that they were at all relevant except for maybe #224, but that turned out not to be helpful. Since you're familiar with the issues in your queue, perhaps you would be willing to throw me a bone and direct me towards one or two tickets that would prove helpful?
@dkreft As well as being called on sign in, the jwt callback is invoked session is checked (e.g via getSession(), useSession(), calling /api/auth/session directly, etc. If you stick a console.log() statement in the call back you should be able to see every time it's called.
The relevant bit from the callback documentation linked to above is in the session callback documentation:
_"The JWT callback is invoked before the session() callback is called, so anything you add to the
JWT will be immediately available in the session callback."_
An example use case for this is getting the latest access token for a third party service before returning it in the session object to the client - useful for services that have short lived / rotating access tokens.
You can then cherry pick what data is exposed from the encrypted JWT to the unencrypted session object is passed to the client - e.g. you might want to expose an access token so the client can make requests in the browser via AJAX, but you might not want to expose a server side API key you have stored in the JWT.
Note: If you don't do anything in the JWT callback, it the session/token expiry will still be updated/extended automatically any time the session is accessed from the client, but the contents of the JWT won't otherwise change.
I've amended the callback documentation to spell this out more clearly.
@iaincollins, Thank you. I'm a little closer to understanding what's going on here but I'm still not seeing the desired results.
When I augment the session with the updated tokens (and add a new datum so I can more easily detect the changes), I can see that the session token is updated with the new data the next time I call getSession(), but the key part I'm missing here is that the updates are not actually persisted across page requests鈥攕o when I refresh the page, the JWT with the expired access token is retrieved and loaded into the session, not the updated version.
Here's the relevant bit of code I've got to handle refreshing and updating the session:
async function getTokens({ refreshAuthToken }) {
const session = await getSession()
const { user } = session
const { tokens } = user
if ( !isTokenStale(tokens.accessToken) ) {
return tokens
}
if ( isTokenStale(tokens.refreshToken) ) {
console.debug('Refresh token is stale')
return
}
const newTokens = await refreshAuthToken({
refreshToken: tokens.refreshToken,
})
session.user.tokens = {
...newTokens,
refreshedAt: new Date(),
}
// After re-fetching the session, I can see in `updatedSession` the values
// that I added in the line above. So that's good...
const updatedSession = await getSession()
console.log('updatedSession: %o', updatedSession)
return newTokens
}
I also added some debugging to my callbacks so I can see when they're invoked:
callbacks: {
jwt(token, profile) {
console.log('JWT callback invoked with %o', { token, profile })
return Promise.resolve(token)
},
session(session, token) {
console.log('session callback invoked with %o', { session, token })
session.user.tokens = token.user.tokens
return Promise.resolve(session)
}
},
I never see my updates reflected in these two methods.
So, the $64,000 question remains....how do I set the changes I made to my session persisted so that when I reload the page, I'm not dealing with stale tokens?
Sorry if I seem really obtuse...I'm not stupid, just blonde, so please go easy on me; and thank you for your continued patience with me.
If you make changes to the token in the jwt callback, they should be persisted in the JSON Web Token (i.e. whatever is returned from it should be saved back to the token). This should be true both on sign in and any other time it is called.
The session callback works differently, it is stateless and the response is not 'saved' anywhere, just returned to the client.
They serve slightly different purposes, but can be used together or separately.
For example, it's possible to use the session callback without using JSON Web Tokens - for example, to get additional data from another database table and return it in the session.
Similarly, some use cases only the the JWT callback and code the token in API routes to perform actions server side and don't need to expose additional data to the client via the session callback.
For example, it's possible to use the session callback without using JSON Web Tokens - for example, to get additional data from another database table and return it in the session.
I had a question about this. I'm currently using the session callback to update session.user.name when the user submits a form. I have written the session callback as such:
session: async (session) => {
const { email } = session.user;
const newSession = session;
const url = `${process.env.SITE}/api/user/${email}`;
const res = await fetch(url, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
if (res.status === 200) {
const user = await res.json();
newSession.user.name = user.name;
}
return Promise.resolve(newSession);
},
And a snippet from my form onSubmit:
const res = await fetch('/api/users', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (res.status === 200) {
await res.json();
getSession();
}
It is working in the sense that the session is being updated after the form is submitted. However, the UI isn't updating after my form is submitted and I call getSession. I have to navigate to a new page for that.
I'm wondering, is this the proper usage of the session callback鈥攖o send a GET request to my API route every time?
And is there a way I can update my UI with the new session.user.name as soon as the form is submitted/session is updated?
I came with the same issue right now and I have agree there is no clear answer for me too.
As the people above I use custom auth (mine with credentials) but the auth schema is common and looks a lot like Spotify's.
You get the access_token and refresh_token on first sign in. access_token is added to every endpoint in Authorization header.
When access_token expires and you get 401 error from one of API endpoint, you have a second chance to regenerate tokens with refresh_token and rerun the point. When you call refresh endpoint, if server responses with 20X app generates all new tokens, so old ones are useless. You need to update session store or jwt but how can I do it?
If session() callback fires everytime I am asking for session info - i can't ask for a refresh token everytime user hard reloads page or opening new tab, if session() callback was designed for it how can I pass data directly to it, ex. getSession(myNewData) or something like that? It looks like it needs to be a base feature.
Or is every custom non-database authorization designed to have no ability to mutate session at all?
Or is there another more efficient way to work with refresh tokens out-of box?
_Edit: I just had an idea to write expire timestamp in my session token after login and check in session() everytime I get there if refresh needs to be done (and refresh there if so). So when my api comes back with 401 I'll ask for getSession() and it refresh itself._
Sorry to bother you, but since I have the lack of experience in auth I find it tricky so I'll be happy if you answer me: Is it a good workaround for my case or it'll cause me troubles in feature?
Edit: I just had an idea to write expire timestamp in my session token after login and check in session() everytime I get there if refresh needs to be done (and refresh there if so). So when my api comes back with 401 I'll ask for getSession() and it refresh itself.
Sorry to bother you, but since I have the lack of experience in auth I find it tricky so I'll be happy if you answer me: Is it a good workaround for my case or it'll cause me troubles in feature?
That is a great solution and is exactly what we want to do in NextAuth.js some time :-)
I was actually just discussing token refreshing in #425 - in the context of databases - but the same logic should apply to tokens used without a database. I very much hope this is something we support natively soon - e.g. with simple functions like getToken('twitter') in the client.
@iaincollins thank you, but I think topic's issue is not clear for me at the moment.
Idea: I update token in jwt callback and resolve session with actual token;
My code, simplified
callbacks: {
session: async (session: any, token: any) => {
console.log('SESSION CALLBACK', session, token);
return Promise.resolve({
...session,
...token
});
},
jwt: async (token: any) => {
console.log('JWT CALLBACK START', token);
let newSession = {
accessExpires: token.accessExpires || getExpireISOCurrentTime(),
...token
};
const isTimeAfter = isAfter(new Date(), new Date(newSession.accessExpires));
if (token && isTimeAfter) {
const result = await sendRefresh(token);
const data = result.data;
if (data.access_token) {
newSession = {
user: data,
accessExpires: getExpireISOCurrentTime()
};
}
}
console.log('NEW TOKEN JWT', newSession);
return Promise.resolve(newSession);
}
}
And this is the log i get after it, with user interactions described since login, simplified:
[user logins]
> JWT CALLBACK START { user: { refresh_token: "vI" } }
> NEW TOKEN JWT { accessExpires: "2020-07-15T13:31:27.257Z", user: { refresh_token: "vI" }}
[page reloads and redirect after success login]
> JWT CALLBACK START { accessExpires: "2020-07-15T13:31:27.257Z", user: { refresh_token: "vI" }} <---- JWT is UPDATED
> NEW TOKEN JWT { accessExpires: "2020-07-15T13:31:27.257Z", user: { refresh_token: "vI" }}
> SESSION CALLBACK { ... accessExpires: "2020-07-15T13:31:27.257Z", user: { refresh_token: "vI" }} <---- on CLIENT I GET THIS
[timeout comes]
[user reloads page]
> JWT CALLBACK START { accessExpires: "2020-07-15T13:31:27.257Z", user: { refresh_token: "vI" }} <---- JWT is EXPIRED
> NEW TOKEN JWT { accessExpires: "2020-07-15T13:41:50.470Z", user: { refresh_token: "qU" }} <---- JWT is UPDATED
> SESSION CALLBACK { ... accessExpires: "2020-07-15T13:31:27.257Z", user: { refresh_token: "qU" }} <---- i get correct JWT in session callback and I pull it on client
> JWT CALLBACK START { accessExpires: "2020-07-15T13:31:27.257Z", user: { refresh_token: "vI" }} <---- JWT is called again?, but why is it old?
> NEW TOKEN JWT { accessExpires: "2020-07-15T13:31:27.257Z", user: { refresh_token: "vI" }} <--- my code crashes since it expired
> SESSION CALLBACK { ... accessExpires: "2020-07-15T13:31:27.257Z", user: { refresh_token: "vI" }} <---- saves as old
Why my JWT token callback calls twice both times and why on the second time it's stale? it updates perfectly after await function but then comes back with old one
What do I miss?
Turns out that server-side getSession is stale in my case.
Is this expected behavior or my code is wrong? After removing server-side check of session described here #345 and here #347 I got rid of second stale jwt callback calls.
SSR and Server-Side redirects are very handy, so can I ask if this bug, wrong usage or expected behaviour? Any workarounds?
To be honest, this behaviour is not obvious at first sight and needs to be described.
@yournatalita Hmm thank you for raising this.
This sounds like it could be a bug but I'm not entirely sure what is going on.
I don't know why and old token would get presented, unless it was some sort of artefact of development mode (where weird things happen, like routes getting called more than once and taking some time to get compiled by Next.js the first time).
If you get the time and are so inclined, please feel free to raise bug report. I've flagged this with bug to make sure it's tracked in the mean time, but we should move that into a separate issue.
It would be helpful to know:
I suspect / hope this is a weird situation from development mode, but I think it's worth some investigation to confirm that.
_And is there a way I can update my UI with the new session.user.name as soon as the form is submitted/session is updated?_
I am having the same issue using the Credentials provider. I am expecting the global session state to be updated const [session] = useSession(); after the form is submitted using my custom React Hook Form const res = await fetch("/api/auth/callback/credentials", requestOptions);
To solve that, now I have to force to refresh the page with router.reload() in order to get the session update after logging in.
Hmm if you have a provider configured in _app.js the session state should update automatically anytime you call getSession - or if other events occur, such as the window losing and gaining focus, or sign in with another window.
There could be edge case issues, but uses a fairly well supported mechanism for this.
Calling getSession() on the client should be enough, you should need to force reload the page, though the provided helper methods actually do that to avoid any jankyness that apps typically display (with the option to use your own sign in function handle it more elegantly left for advanced users).
If you have a provider in _app.js and call getSession() after the sign in form has returned and it's still not working I'd love to take a look.
Thanks for the quick reply.
I have added provider in _app.tsx, and I am using a modal box for the login form. And I am expecting after the login form has been submitted successfully, and the modal box will be closed, and then the header login button detect the session update from const [session] = useSession();, and then button will be replaced with a Logout button without a page refresh.
I have also tried to call getSession(), the returned value is up to date, but the useSession hook session state is still not up to date unless reload the page or as you said the window losing and gaining focus after coming back from another window, and then the session state will be updated.
This is a demo video to show the issue. https://www.loom.com/share/9e4837edbd274ec892e5853e6a9c9341
Probably next-auth hasn't been designed to work in this way. But if we have a hook or function to get the session update instantly within the same window or screen, this would be great to address an use case like this.
I'm running into a similar issue described above, but I don't have the same ability as others to depend on a timeout to refresh the session. The issue I'm running into is not finding a way to update the user object that is returned from getSession().
I'm using the credentials provider so that I can authenticate with our custom API endpoint on another server. On a successful login, I fetch some basic user information from our API as well to populate the user object that is attached to the session.
The use case I'm having difficulty understanding how to implement properly is: if the logged in user updates their name or profile image, I can't find a way to update this information in the returned object from getSession() without:
Option 1 isn't really acceptable for obvious reasons: you shouldn't have to force the user to logout just to display their new profile image / name change.
Option 2 isn't acceptable either, as multiple getSession calls are made on a regular basis from the client, and would quickly add an enormous amount of traffic to our APIs.
Ideally, I'd be able to add some kind of flag to the session to trigger a refetch of data during the jwt / session callback as described by @yournatalita: getSession(myNewData).
My only option I can see now is to add some sort of timeout to the session so I can throttle my api call checks, but this still isn't really ideal and adds more traffic than is necessary to our APIs, along with causing at least some kind of delay to updating the user's image / name depending on how long I add the timeout for.
Is there some way I can alter just one property on the session object from the client so it knows to refetch user data from our API? Or is there a REST endpoint that's not documented where I can update the session from the client? Or just pass some bit of data to the jwt / session callback? I've been searching through all the issues and docs and I've come up empty-handed.
Any help is greatly appreciated!
The issue I'm running into is not finding a way to update the user object that is returned from getSession().
You can do this via the session callback
Ideally, I'd be able to add some kind of flag to the session to trigger a refetch of data during the jwt / session callback as described
If you call getSession() on the client it should update the session state in the current window and in all other open windows.
_e.g. you can do this after an operation like saving changes to a profile - e.g. updating name or profile image._
If you want to control how the session on the client is updated, you can can control the cache behaviour by passing options in app.js
import { Provider } from 'next-auth/client'
export default function App ({ Component, pageProps }) {
return (
<Provider session={pageProps.session}
options={{
clientMaxAge: 60 // Re-fetch session if cache is older than 60 seconds
}}
>
<Component {...pageProps} />
</Provider>
)
}
Perhaps I'm missing something here but if I mutate the session returned from getSession() I do not see a change in the session callback.
session: async (session, user, sessionToken) => {
// session and user remain the same.
return Promise.resolve(session)
},
jwt: async (token, user, account, profile, isNewUser) => {
// user remains the same.
if (profile) {
token.id = user.id
}
return Promise.resolve(token)
}
export default async (req, res) => {
const session = await getSession({ req })
session.user.foo = 'foobar'
await getSession({ req })
res.end()
}
If you want to control how the session on the client is updated, you can can control the cache behaviour by passing options in app.js
import { Provider } from 'next-auth/client' export default function App ({ Component, pageProps }) { return ( <Provider session={pageProps.session} options={{ clientMaxAge: 60 // Re-fetch session if cache is older than 60 seconds }} > <Component {...pageProps} /> </Provider> ) }
This was the big piece I was missing. I noticed the client would call session almost constantly when even just regaining focus to the browser window or just clicking around throughout the app, so I thought it would be constantly polling my API to fetch the data I needed. Adding the caching options instantly fixed that issue and allowed me to use the session callback as you mentioned.
For someone else that might be running into this issue, here's how I went about solving it:
_app.js
import { Provider } from 'next-auth/client';
const sessionOptions = {
clientMaxAge: 2 * 60 * 60, // Re-fetch session if cache is older than 2 hours
keepAlive: 60 * 60 // Send keepAlive message every hour
};
function App({ Component, pageProps }) {
const [session, setSession] = useState(pageProps.session);
return (
<Provider options={sessionOptions} session={pageProps.session}>
<Header {...pageProps} session={session} />
<Component {...pageProps} updateSession={setSession} />
</Provider>
)
}
export default App;
pages/api/auth/[...nextauth].js
...
const options = {
callbacks: {
jwt: async (token, authToken) => {
let resolvedToken = token;
if (authToken) {
resolvedToken = { ...authToken, ...token };
}
return Promise.resolve(resolvedToken);
},
session: async (session, jwt) => {
if (jwt.authToken) {
try {
// fetches the user record from our API
const contact = await getContact(jwt.authToken);
// Only store the data we need in the session.user object
const minimalContact = {
email: contact.email,
image: contact.profile_img.thumbnail ? contact.profile_img.thumbnail : null,
role: contact.role,
name: contact.first_name
};
session.user = minimalContact;
return Promise.resolve(session);
} catch (err) {
return Promise.resolve(session);
}
}
return Promise.resolve(session);
},
...
providers: [
Providers.Credentials({
authorize: async (credentials) => {
// my API returns an authToken to be used on all subsequent API calls
const authToken = await login(credentials);
// No authToken implies the user entered incorrect credentials
if (authToken === null) {
return Promise.resolve(null);
}
// email is also useful for identifying the user in my application for subsequent API calls
return Promise.resolve({ authToken, email: credentials.email });
},
credentials: {
email: { label: 'Email', type: 'email', placeholder: 'Email' } ,
password: { label: 'Password', type: 'password' }
},
name: 'Credentials'
})
]
},
...
editProfile.js
import { getSession } from 'next-auth/client';
....
// Note updateSession is passed in from my _app.js to the page.
// This is so in the header I can display the updated profile image and username
// (if changed on a profile update)
export default function EditProfile({ updateSession }) {
const saveData = (data) => {
saveContact(data).then(() => {
getSession().then((session) => {
updateSession(session);
})
});
})
};
}
...
Hope that helps someone else that might be in the same spot struggling with how to do something like this. I'm not sure I did it the best way possible but it works as I would want it to without unnecessary API calls! :)
Thanks @dsavage311! Avoiding unnecessary API call is exactly what I'm trying to do. This clears up some things but in regards to updateSession, I understand you passing it in from _app.js but where does it come from before that / what does it look like? The adapter you are using? If so, how does one access that method?
@justinwhall it looks like the updateSession is updating the session that gets passed into the <Header> component. This isn't an ideal solution for my use case, as I'd like to update the session globally without passing down method through props.
I'm still working on a robust solution to update an existing user's access token which is stored in session, but can't quite figure out how the API wants me to update the session directly. Using the session callback as suggested above seems to result in a stale session when subsequently calling getSession() after mutating. If I figure it out, I'll put an update here.
@justinwhall it looks like the
updateSessionis updating the session that gets passed into the<Header>component. This isn't an ideal solution for my use case, as I'd like to update the session globally without passing down method through props.
Ah, I missed that. Thanks @elilambnz! I think my issue is around this. For instance ppageProps.session is always undefined for me.
function App({ Component, pageProps }) {
console.log(pageProps.session) // always undefined
return (
<Provider options={sessionOptions} session={pageProps.session}>
<Header {...pageProps} session={session} />
<Component {...pageProps} updateSession={setSession} />
</Provider>
)
}
I think this might be an issue with my adapter. I'm using the custom adapter option with Fauna. https://gist.github.com/justinwhall/4895b7d6115b2acf7201808109338da7
Edit: Erm, maybe not. The createSession method is only for DB session not jwt.
@justinwhall the way you're accessing pageProps.session above looks correct, this might be a separate issue. I'll add my solution to updating the session in a separate comment, if that doesn't solve it perhaps open a new issue for this problem.
An example of updating the session in the context of extending a user upon signin can be found here.
However, this doesn't cover conditionally updating the session. As far as I'm aware, there's not currently a method exposed to update the session. However, whenever the session is accessed, the jwt() callback is invoked before the session() as per the docs. In the context of refreshing an accessToken, which is my use case for updating the session, we can use the callbacks to check the token expiry and pass new data to the session. As for updating user information, i.e. when a user is editing their profile, you could perhaps adapt this solution to check for a difference in user data when the callbacks are invoked, then pass the data into the session.
callbacks: {
jwt: async (token, user, account, profile, isNewUser) => {
// The user argument is only passed the first time this callback is called on a new session, after the user signs in
if (user) {
// Add a new prop on token for user data
token.data = user
}
// Don't access user as it's only available once, access token.data instead
if (token.data?.accessToken) {
const decodedJwt = jwt_decode(token.data.accessToken)
const almostNow = moment().add(5, 'minutes').valueOf() / 1000
if (decodedJwt.exp !== undefined && decodedJwt.exp < almostNow) {
// Token almost expired, refresh
try {
const newToken = await refreshToken(token.data.refreshToken)
token.data.accessToken = newToken
} catch (error) {
console.error(error, 'Error refreshing access token')
}
}
}
return Promise.resolve(token)
},
session: async (session, user) => {
// `user` is the jwt token
if (user.data) {
// Assign access token to session
session.accessToken = user.data.accessToken
return Promise.resolve(session)
},
},
Here I am using the credentials provider with JWT. If you're specifically after a solution to refresh access tokens, this approach assumes that you have an API or are using a service which provides accessToken and refreshToken JWTs, and also provides functionality to refresh a token.
@elilambnz Your logic makes perfect sense. Appreciate your response here. And yes, I am doing what you are suggesting. Updating a user's profile. The above makes sense as you can check to see if a token is expired and then, do stuff...
In the case of a user, say, updating their profile picture, there's no way to update the session because no matter what I do, the session param is _always_ the same. In fact, all the params for the session and jwt call back are _always the same_. For instance:
// some API route or page.
export default async (req, res) => {
const session = await getSession({ req }) // get session
session.user.foo = 'foobar' // mutate session
// do other stuff that doesn't matter...
}
// no matter how I mutate the session, params are always the same. Specifically, I'd expect `session.user.foo` === 'foorbar'
session: async (session, user, sessionToken) => {
session.user.id = user.id
return Promise.resolve(session)
},
jwt: async (token, user, account, profile, isNewUser) => {
if (profile) {
token.id = user.id
}
return Promise.resolve(token)
}
Thanks @dsavage311! Avoiding unnecessary API call is exactly what I'm trying to do. This clears up some things but in regards to
updateSession, I understand you passing it in from_app.jsbut where does it come from before that / what does it look like? The adapter you are using? If so, how does one access that method?
@justinwhall So I'm actually just using the React useState hook in _app.js. The initial value is what is passed in from pageProps, but after that I'm manually updating it. So I pass the useState update function down to my page components as updateSession.
import { useState } from 'react';
function App({ Component, pageProps }) {
const [session, setSession] = useState(pageProps.session); // this is all I'm doing
return (
<Provider options={sessionOptions} session={pageProps.session}>
<Header {...pageProps} session={session} />
<Component {...pageProps} updateSession={setSession} />
</Provider>
)
}
For clarity's sake, I probably should have passed it into my component as setSession. So, pageProps gives the initial value from the SSR page, but then when I do something in the UI like update the user's image or name, I call the next-auth's getSession and use it in that passed down setSession from the useState react hook (although it would be referred to as updateSession in my above code). Since I really only care about the current session in the header, header is always using the session I am holding in state.
Hopefully that clears up your question. Updating the actual session object happens in the callbacks on the server side in the nextAuth config. The only place you can mutate session is in these callbacks. I do the actual mutation in the session callback, but if you notice I also spread that data across the jwt token. I'm not sure that's done in the "proper way", but that effectively preserves the data that I'm trying to save in the session:
callbacks: {
jwt: async (token, authToken) => {
let resolvedToken = token;
if (authToken) {
resolvedToken = { ...authToken, ...token };
}
return Promise.resolve(resolvedToken);
},
session: async (session, jwt) => {
if (jwt.authToken) {
try {
// fetches the user record from our API
const contact = await getContact(jwt.authToken);
// Only store the data we need in the session.user object
const minimalContact = {
email: contact.email,
image: contact.profile_img.thumbnail ? contact.profile_img.thumbnail : null,
role: contact.role,
name: contact.first_name
};
session.user = minimalContact;
return Promise.resolve(session);
} catch (err) {
return Promise.resolve(session);
}
}
return Promise.resolve(session);
},
In the case of a user, say, updating their profile picture, there's no way to update the session because no matter what I do, the session param is always the same. In fact, all the params for the session and jwt call back are always the same.
@justinwhall I believe this is due to the nature of JS (and admittedly an oversight of mine earlier). In your example, you're essentially taking a _copy_ of session and updating the value. So session.user.foo will be foobar in the scope of that function, but the original session object is unchanged.
Since the library doesn't appear to expose a method for updating the session directly, that leaves us a couple of options. We could provide a mutateable session object to the <Provider> and make an updateSession() function for mutating this value (@dsavage311 has provided an example of this while I was typing all of this out). With the introduction of React Hooks, you could possibly provide this functionality globally without passing it down as props, which is something I'm trying to avoid myself.
Another (less clean) approach is manually triggering a signin after updating the user. Assuming the API providing user data has the new changes, the new user object will be loaded into the session if you're saving user details in the callbacks.
Updating the actual session object happens in the callbacks on the server side in the nextAuth config. The only place you can mutate session is in these callbacks. I do the actual mutation in the session callback, but if you notice I also spread that data across the jwt token. I'm not sure that's done in the "proper way", but that effectively preserves the data that I'm trying to save in the session
@dsavage311 since these callbacks are invoked quite regularly, do you find that this approach spams your API? Or are you only fetching the session in one place within your app? I'm accessing the session from a couple of places and I'd imagine this approach would generate a lot of surplus API calls.
If you call
getSession()on the client it should update the session state in the current window and in all other open windows._e.g. you can do this after an operation like saving changes to a profile - e.g. updating name or profile image._
@iaincollins, I'm calling getSession() like this but the only way to get the updated session on my component (A navbar in my case) is to refresh the page or loose/regain focus. Is there another way to refetch the session data?
I have the same issue. I use a lot of tricks to force an update of the JWTtoken after a user update its profile, however this is very clucky, and sometimes it does not work (the old version remains on the client side, and after a browser refresh the old username is back again).
I am really looking forward a stable and solid API solution to update the session data.
[Edit] My solution does not work at all in Vercel environment. I will fall back to calling the database every time a user visits the website instead of accessing basic profile information from the JWT token as there is no way to mutate it consistently with next-auth for now.
Been looking at this issue, since I need to update claims inside jwt token. However, I just found though that we trying to achieve a non JWT spec compliant practice. Any change to token's payload requires the "user" (or at least the auth server) to sign the new token, which in turn should be after user authentication.
As far as I understand, the best practice should be to force a new signIn after payload change, ideally using the refresh token to avoid the need for user input. Is is possible to force the signIn to use the refresh token?
If you call
getSession()on the client it should update the session state in the current window and in all other open windows.
_e.g. you can do this after an operation like saving changes to a profile - e.g. updating name or profile image._@iaincollins, I'm calling getSession() like this but the only way to get the updated session on my component (A navbar in my case) is to refresh the page or loose/regain focus. Is there another way to refetch the session data?
It sounds like there is a bug in the intended behaviour, getSession() should trigger the same update event as gaining or losing focus, but a few folks have reported it's apparently not doing that in this scenario.
If someone could raise a bug report about getSession() not working as expected that would be really helpful (so I can track it and it doesn't get missed). I'd like to fix that in the next release.
Been looking at this issue, since I need to update claims inside jwt token. However, I just found though that we trying to achieve a non JWT spec compliant practice.
What part of the spec are you referring to?
Any change to token's payload requires the "user" (or at least the auth server) to sign the new token, which in turn should be after user authentication.
NextAuth.js updates the token (creating a new token, signing it and replacing the old token) whenever the contents of the JWT changes. This including extending the expiry to prevent expiry of an active sessions. There are currently no plans to change this.
As far as I understand, the best practice should be to force a new signIn after payload change, ideally using the refresh token to avoid the need for user input. Is is possible to force the signIn to use the refresh token?
I don't know what you mean here. (What Refresh Token? - What generates this token and where is stored?) I think that sounds like a flow for a different scenario than the context of how we use a JSON Web Token in NextAuth.js.
If you call
getSession()on the client it should update the session state in the current window and in all other open windows.
_e.g. you can do this after an operation like saving changes to a profile - e.g. updating name or profile image._@iaincollins, I'm calling getSession() like this but the only way to get the updated session on my component (A navbar in my case) is to refresh the page or loose/regain focus. Is there another way to refetch the session data?
It sounds like there is a bug in the intended behaviour, getSession() should trigger the same update event as gaining or losing focus, but a few folks have reported it's apparently not doing that in this scenario.
If someone could raise a bug report about getSession() not working as expected that would be really helpful (so I can track it and it doesn't get missed). I'd like to fix that in the next release.
This might be an appropriate use for my issue https://github.com/nextauthjs/next-auth/issues/596. I believe this is the exact same problem I encountered in that issue, so let me know what I can do to make it suitable for your use.
@blms Perfect, thanks!
I'm going to close this and ask folks to track that ticket (#596), which I've tagged accordingly.
Thanks to everyone for their input!
Most helpful comment
@justinwhall the way you're accessing
pageProps.sessionabove looks correct, this might be a separate issue. I'll add my solution to updating the session in a separate comment, if that doesn't solve it perhaps open a new issue for this problem.