-
Notifications
You must be signed in to change notification settings - Fork 126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Interceptors] Creating an API client with automatic token refresh functionality #79
Comments
Yes, and even more, looks like that some essential types that are required to implement mutating interceptors are not exposed as a public API, so you cannot just wrap ohmyfetch by providing custom implementation of So the side issue is: please expose all typings that are currently available at Side issue 2: please add some sort of userdata field in |
Thanks for the feedback @evfoxin @attevaltojarvi As a summary what I could understand is missing:
Actually it is currently possible to achieve this by modification to the context but it is probably more convenience to allow chaining. |
@pi0 I have a follow-up issue with this one. While waiting for an update for the points raised, I implemented the API client + authentication/token refresh using a recursive approach. It goes something like this: export const useAPIClient = () => {
const doRequest = async (method, endpoint, config: FetchOptions) => {
const { authClient, refreshSession, invalidateSession } = useAuthProxyClient()
const client = authClient.create({ baseURL: <our API url> })
const authCookie = useCookie('authTokens')
if (authCookie.value) {
config.headers = { ...config.headers, Authorization: `Bearer ${authCookie.value.accessToken}` }
}
try {
return await client(endpoint, { method, ...config })
} catch (requestError) {
const refreshToken = authCookie.value.refreshToken
if (!requestError.response?.status === 401 || !refreshToken) {
// Legitimate 4xx-5xx error, abort
throw requestError
}
try {
await refreshSession(refreshToken)
// call function recursively after refreshSession has done a request to /api/oauth/refresh API route and updated the cookie
return await doRequest(method, endpoint, config)
} catch (refreshError) {
await invalidateSession()
await navigateTo('/login')
}
}
}
return {
doRequest
}
}
export const useAuthProxyClient = () => {
const authClient = $fetch.create({ retry: 0 })
const authCookie = useCookie('auth')
const refreshSession = async refreshToken =>
authClient('/api/oauth/refresh', { method: 'post', body: { refreshToken, ... } })
.then(response => {
return { <access and refresh token values from response> }
})
.then(tokens => { authCookie.value = tokens })
const invalidateSession = async () =>
authClient('/api/oauth/revoke', { method: 'post', body: { ... } })
.then(() => { // ignore errors })
return {
authClient,
refreshSession,
invalidateSession
}
} The API routes are in Nuxt's Using the onRequestError interceptor example from the library's README: async onRequestError ({ request, error }) {
console.log('[fetch request error]', process.server, process.client, request, error)
} I get
respectively. Really don't know how to fix this, and as said, this only happens on first page load. If I request access tokens successfully, go to our API admin panel and revoke them manually, and then in the Nuxt app go to a different page (I have a page middleware that tries to use the API client to fetch my /me/ endpoint), the whole process works; I understand this is not 100% an ohmyfetch issue, but since you also contribute to Nuxt, I thought that you could help me with this. |
An update to the above: this was a Nuxt issue. I had |
+1 |
Hey guys I repurposed the https://www.npmjs.com/package/@nuxtjs-alt/http |
Hi, I'm having a similar flow/issue with Nuxt 3 and was wondering if:
Sorry to bother but I'm starting a project with Nuxt 3 at work. I'm just trying to avoid any issues. Cheers |
Hello guys, is there any news about this feature? |
@mrc-bsllt I've been happy with my custom wrapper approach, give that a try? |
@mrc-bsllt wrap // request.ts
import type { FetchRequest, FetchOptions, FetchResponse } from 'ofetch';
import { ofetch } from 'ofetch';
const fetcher = ofetch.create({
baseURL: process.env.API_URL + '/api',
async onRequest({ options }) {
const accessToken = localStorage.getItem('accessToken');
const language = localStorage.getItem('language');
if (accessToken) {
options.headers = {
...options.headers,
Authorization: `Bearer ${accessToken}`,
};
}
if (language) {
options.headers = {
...options.headers,
'Accept-Language': language,
};
}
},
async onResponse({ response }) {
if (response.status === 401 && localStorage.getItem('refreshToken')) {
const { accessToken } = await ofetch('/auth/token', {
baseURL: process.env.API_URL + '/api',
method: 'POST',
body: {
accessToken: localStorage.getItem('accessToken'),
refreshToken: localStorage.getItem('refreshToken'),
},
});
localStorage.setItem('accessToken', accessToken);
}
},
});
export default async <T>(request: FetchRequest, options?: FetchOptions) => {
try {
const response = await fetcher.raw(request, options);
return response as FetchResponse<T>;
} catch (error: any) {
if (error.response?.status === 401 && localStorage.getItem('refreshToken')) {
const response = await fetcher.raw(request, options);
return response as FetchResponse<T>;
}
return error.response as FetchResponse<T>;
}
}; |
Hi @Shyam-Chen, how can I use this solution with the useAsyncData? |
@mrc-bsllt I'm not wrapping to the composition API. <script lang="ts" setup>
import request from '~/utilities/request';
onMounted(async () => {
const response = await request<UserList>('/user-list', { method: 'POST', body: {} });
users.value = response._data.result;
});
</script> |
Is there any way to make a wrapper/composable so the API using composition (like using useFetch) remains same? |
/**
* wrapping
*/
import { useFetch } from '~/composables';
const todos = useFetch('/todos').json<Todos>();
const todosId = ref<TodoItem['_id']>('');
const todosById = useFetch(computed(() => '/todos/' + todosId.value)).json<TodosById>();
const getTodos = async () => {
await todos.post({}).execute();
console.log(todos.data.value);
};
const getTodoItem = async (id: TodoItem['_id']) => {
todosId.value = id;
await todosById.get().execute();
console.log(todosById.data.value);
};
/**
* not wrapping
*/
import request from '~/utilities/request';
const getTodos = async () => {
const response = await request<Todos>('/todos', { method: 'POST', body: {} });
console.log(response);
};
const getTodoItem = async (id: TodoItem['_id']) => {
const response = await request<TodosById>(`/todos/${id}`, { method: 'GET' });
console.log(response);
}; |
@attevaltojarvi I have been using axios to intercept response and request using the example that you mentioned but now I am trying to use ofetch and needed the same functionality on onRequest and onResponse.
Can you please help me with onResponse in ofetch doing same functionality as in axios
|
Instead of ofetch.create({
// ...
async onResponse({response}) {
// yadda yadda yadda
},
// ...
}) you'd use useFetch(url, {
// ...
async onResponse({response}) {
// blah blah blah
},
// ...
}) , |
@Shyam-Chen thank you for your example provided. Can you please show how the |
@kompetenzlandkarte I'm sorry, but I haven't packaged it in a composable way at the moment. I use The link below shows how I created |
I think such composable would fit in best in nuxt auth module instead of adding to ofetch core size . |
@pi0 I see what you mean but this kind of issue has been seen so many times when I searched for the answer. If nothing else, it would be great to add like an example to the docs -- of composable that can handle token refresh, might help a lot of people using separate backend with such strategy. Would add it myself but still haven't fully figured it out. |
Hi Guys, I Have created new composable for automatic token refresh.
You can use it as useFetch! |
Hi!
We talked about this yesterday over on the Nuxt discord's #nuxt3 channel, but since it's more of an ohmyfetch thing, I decided to create the issue here instead of the nuxt/framework repo.
We're currently firing up a development effort for a new Nuxt3 project in our company (we've previously used Nuxt2 in some projects). Axios was the preferred way of doing API requests previously but since ohmyfetch is now the recommended choice, I thought of rewriting our API client wrapper to use ohmyfetch instead of Axios.
I'm sure the problem is familiar to most if not all of you: how to write an API client that can automatically refresh your access tokens behind the scenes? This is pretty much our previous solution that uses Axios' interceptors:
Historically we've done this by creating one larger
doRequest
function that gets called recursively, but it was refactored to use interceptors.I'd like to replicate this functionality using ohmyfetch, so that I could do something like:
and the access tokens get set to request headers and possibly refreshed automatically.
I was checking out ohmyfetch's interceptors, but apparently they don't return anything, only functioning as hooks where you can set up logging or do something else, instead of acting as sort of middleware like Axios' counterparts. This leads me to the conclusion that this probably isn't currently possible with ohmyfetch? It's no problem to use the recursive approach we had previously, but I feel that this is a bit of a shortcoming with the current interceptors.
The text was updated successfully, but these errors were encountered: