Open septatrix opened 3 months ago
I believe there should be a refresh token implementation. Is this open to PR?
Refresh tokens are not implemented so far as we just give back to the session what's needed and some OAuth does not handle refresh tokens.
Do you have an example of an implementation you would like to see?
@septatrix I have successfully achieved the refresh of the session with the session hook for 'fetch'. If the session has expired and I have a valid refresh token, then the refresh workflow is initiated to obtain a new valid token.
@septatrix I have successfully achieved the refresh of the session with the session hook for 'fetch'. If the session has expired and I have a valid refresh token, then the refresh workflow is initiated to obtain a new valid token.
Would you mind sharing the code for that?
@PGLongo I would be also very interested in that! 😊
Sure! Here I refresh the Microsoft Oauth. Note that in the auth handler I have stored the expirationDate
in the session.user
// server/plugins/session.ts
import { useRuntimeConfig } from '#imports'
import type { OAuthMicrosoftConfig } from '~/server/api/auth/login.get'
export default defineNitroPlugin(() => {
sessionHooks.hook('fetch', async (session, event) => {
const now = new Date()
const expirationDate = new Date(session.user.expirationDate)
const jwt = getCookie(event, 'jwt')
console.log(expirationDate < now, expirationDate, now)
if (expirationDate < now || !jwt) {
const config = useRuntimeConfig(event).oauth?.microsoft as OAuthMicrosoftConfig
const tokenEndpoint = `https://login.microsoftonline.com/${config.tenant!}/oauth2/v2.0/token`
const params = new URLSearchParams()
const refreshToken = getCookie(event, 'refresh-token') || ''
params.append('client_id', config.clientId!)
params.append('client_secret', config.clientSecret!)
params.append('refresh_token', refreshToken)
params.append('grant_type', 'refresh_token')
const data = await $fetch(tokenEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params
})
const now = new Date()
session.user.expirationDate = new Date(now.getTime() + data.expires_in * 1000)
await setCookie(event, 'jwt', data.access_token, { httpOnly: true, secure: true, maxAge: data.expires_in })
}
})
sessionHooks.hook('clear', async (session, event) => {
await deleteCookie(event, 'jwt')
await deleteCookie(event, 'refresh-token')
})
})
@PGLongo Awesome! Very kind of you! Thank you very much!
@PGLongo Awesome! Very kind of you! Thank you very much!
Sharing is caring! 😊
If you have any questions or need further assistance, feel free to reach out. Happy coding!
Thank you, @PGLongo, for providing an example implementation of the refresh dynamic!
Inspired by your code, I created a similar plugin that refreshes the tokens when the access token expires. The problem I'm facing is that the sealed session cookie is never updated, so the original contents remain unchanged. After the access token expires the first time, it refreshes the tokens on every subsequent page refresh. Do you have a solution for this issue?
My code:
// server/plugins/session.ts
export default defineNitroPlugin(() => {
sessionHooks.hook('fetch', async (session, event) => {
const authenticationConfig = getAuthenticationConfig(event) // Configuration helper
const now = new Date()
const expirationDate = new Date(session.expirationDate)
if (expirationDate < now) {
// Refresh session
const body = new FormData()
body.append('grant_type', 'refresh_token')
body.append('refresh_token', session.refreshToken)
body.append('response_type', 'id_token')
body.append('client_id', authenticationConfig.clientId)
body.append('client_secret', authenticationConfig.clientSecret)
body.append('scope', authenticationConfig.scope)
const token = await $fetch<AccessToken>(authenticationConfig.tokenURL, {
method: 'post',
body
})
session.accessToken = token.access_token
session.refreshToken = token.refresh_token
session.expirationDate = new Date(now.getTime() + token.expires_in * 1000)
}
})
})
@PGLongo do you have an answer on the question of @thijsw ? We are really looking forward to it.
@doubleujay and @thijsw are you refreshing the token on server side?
@PGLongo Yes, what I'm trying to accomplish is that the tokens is refreshed server-side (when expired) and the new updated value stored in the encrypted cookie. This new, just retrieved, token should be used by all API calls within the same request cycle. However, using my previous shared code, this doesn't work. The cookie doesn't get updated, so when I refresh the page the old token is being refreshed again. Do you have a suggestion how I could address this?
@PGLongo Is the reply of @thijsw enough to give more insight?
Inspired by your code, I created a similar plugin that refreshes the tokens when the access token expires. The problem I'm facing is that the sealed session cookie is never updated, so the original contents remain unchanged. After the access token expires the first time, it refreshes the tokens on every subsequent page refresh. Do you have a solution for this issue?
I can maybe provide some insight here, dealing with this myself at the moment.
The reason the cookie may not be getting refreshed is because the set-cookie
header is not forwarded on fetch calls.
This eventually lead me to a fetch wrapper snippet like this:
const res = await $fetch.raw<T>(request, {
...opts,
headers: { ...opts?.headers, ...useRequestHeaders(['cookie']) },
})
// forward cookies into SSR response
const cookies = (res.headers.get('set-cookie') || '').split(',')
for (const cookie of cookies)
appendResponseHeader(event, 'set-cookie', cookie)
// Return the data of the response
return res._data
The cookie now get set refreshed properly for me but pressing back on the browser messes this up and theres an nuxt internal error. All this trouble kind of made me rethink my token flow, I do not see a clean refresh token implementation possible on Nuxt, or SSR flows in general, where server side refreshes are needed. My conclusion here is to just ditch the refresh flow for a regular access token + auto session extension on activity + revocation flow.
Hope it helps a bit at least :)
Does the refreshCookie
util help with the stale cookie issue reported by @thijsw and @daniandl? Documentation is here https://nuxt.com/docs/api/utils/refresh-cookie
Hi, currently also running into the same problem. When I replaceUserSession
with new experiationDate the cookie is not updated and the session data also stays the same. Any idea how to replace the session when the refresh token is still valid? Thanks!
@sdevogel are you calling await fetch()
from useUserSession() in the client after you replaced the session in the server?
@sdevogel are you calling
await fetch()
from useUserSession() in the client after you replaced the session in the server?
No, how would the client know that the server replaced the session? Maybe asking for the obvious but I'm a bit lost. Thanks a lot for your help :) @carlos-duran
I saw the
"offline_access"
scope being used for the OAuth0 provider but no reference to refresh tokens in the codebase. Are refresh tokens implemented/utilized? Or is the session from the OAuth2 provider only used once and afterwards everything is delegated to h3?