nuxt-graphql-middleware
nuxt-graphql-middleware copied to clipboard
[Question] Retry original operation
Hello, awesome project. I'm moving my app to this package instead nuxt-apollo.
With apollo we could use onError to create errorLink on which we've been refreshing the tokens and then performing original query/mutation again:
case 'UNAUTHENTICATED':
return fromPromise(
refreshAccessToken(`${refreshToken}`, `${codeVerifier}`, event),
)
.filter(value => Boolean(value))
.flatMap((accessToken) => {
const oldHeaders = operation.getContext().headers
operation.setContext({
headers: {
...oldHeaders,
authorization: `Bearer ${accessToken}`,
},
})
return forward(operation)
I've seen onServerError in docs:
import type { H3Event } from 'h3'
import type { FetchError } from 'ofetch'
export default defineNuxtConfig({
graphqlMiddleware: {
onServerError(
event: H3Event,
error: FetchError,
operation: string,
operationName: string,
) {
event.setHeader('cache-control', 'no-cache')
return {
data: {},
errors: [error.message]
}
}
}
})
With above configuration we could set new httpOnly cookies with tokens when we detect 401 response from our graph.
After that new tokens will be added automatically by serverFetchOptions where we take access_token from httpOnly cookie and add as header to the request. (we can do because all happens on nuxt server)
But my concern is there is no forward argument in the onServerError so I wonder is it possible to somehow retry the original mutation/query which failed after setting new cookies with just refreshed tokens?
This sounds reasonable. I will think about how this could be implemented in a good way.
This sounds reasonable. I will think about how this could be implemented in a good way.
Awesome, thank you so much. It doesn't really need to be implemented like in apollo - I see that operation here is something different from what it is in apollo.
It could be done differently but I should be able to decide when to execute this retry as I want first to set new cookies (which is async operation - it requires call to api).
Later I want to call retry and hope that serverFetchOptions will pick up new values from cookies and set header with refreshed token.
This project is perfect for people who want to store tokens with httpOnly flag and use nuxt server to handle all communication with graphql so that it's impossible to get tokens from client side javascript. I think this is big value of this project and I really appreciate your work.
PS: I'm implementing this for quite large Swiss science org :)
I looked into this and I see one problem:
As most requests to the Nuxt GraphQL server route will originate from a Nuxt app context (e.g. page component, plugin), they will be executed in parallel. This would result in let's say 4 requests with stale token cookies being retried in the server route until the first one sends back a Set-Cookie header with the updated token. This would then trigger 12 requests in total: The original 4 /api/graphql/query request from the Nuxt app that lead to a 401, the 4 "token refresh" requests made in onServerError and then the 4 retried requests. You may also end up with 4 different tokens.
From my limited understanding about Apollo I assume that it can handle this better because the GraphQL requests are done "directly" from within the app context. It is able to use a single "link" instance to handle such authentication errors, which makes sure that the token refresh request is only happening once.
However: It would actually be possible to do this yourself with the current provided functionality of the module. When the serverFetchOptions option is provided (a method) it expects you to return the FetchOptions for ofetch. These are the options used for the request to the actual GraphQL server.
https://github.com/unjs/ofetch/blob/bf77dd249e4eede3ba3ec29a59c91c01fcff150e/src/fetch.ts#L39
Because ofetch supports retrying and interceptors, you might be able to set a retry value of 1 and add an onResponse or onResponseError interceptor, where you can retrieve a new token and pass it inside the context provided by these methods.
But while thinking about possible solutions I came to the conclusion that it might be better to let module users handle the GraphQL request part themselves and provide a config option for that. That way custom retry logic could be implemented fairly easily, in addition to all kinds of other customizations.
If you install the latest beta using [email protected] you will now be able to implement the desired functionality using the new doGraphqlRequest option.
I've already updated the docs for the upcoming 4.0.0 release that contains an example how such a retry feature could be implemented:
https://nuxt-graphql-middleware.dulnan.net/configuration/server-options.html
All options that are needed in the server build have been moved to a custom file, so you'll have to create a folder called app in your Nuxt root and create a file named graphqlMiddleware.serverOptions.ts in it. There you can paste the example from the docs and adapt it to your needs.
Please let me know if everything works as expected. I gave it a try in one of my current projects and it worked. Once you've confirmed as well I will release the 4.0.0 version.