Redux-Toolkit query interceptor
Asked Answered
T

2

12

I'm trying to build an interceptor for cases when the access token becomes invalid with RTK Query. I've built it by an example in the docs, so that is looks as follows:

const baseQuery = fetchBaseQuery({
    baseUrl: BASE_URL,
    prepareHeaders: (headers, { getState }) => {
        const {
            auth: {
                user: { accessToken },
            },
        } = getState() as RootState;
        if (accessToken) {
            headers.set('authorization', `Bearer ${accessToken}`);
        }
        return headers;
    },
});

const baseQueryWithReauth: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (
    args,
    api,
    extraOptions
) => {
    let result = await baseQuery(args, api, extraOptions);

    if (result.error && result.error.status === 401) {
        const refreshResult = await baseQuery('token/refresh/', api, extraOptions);

        if (refreshResult.data) {
            api.dispatch(tokenUpdated({ accessToken: refreshResult.data as string }));

            // retry the initial query
            result = await baseQuery(args, api, extraOptions);
        } else {
            api.dispatch(logout());
        }
    }
    return result;
};

export const baseApi = createApi({
    reducerPath: 'baseApi',
    baseQuery: baseQueryWithReauth,
    endpoints: () => ({}),
});

The problem is that the token/refresh/ expects a POST request with a refresh token its body and I can't figure out how to rebuilt this line const refreshResult = await baseQuery('token/refresh/', api, extraOptions); for it to accept parameters and make POST request.

Tuition answered 29/7, 2021 at 18:22 Comment(5)
It'd be also nice to know how to separate requests to private and public endpointsTuition
I'm curious to know how do you make all the other api slices extend from this baseQueryWithReauthCornelie
@Jose Bernhardt Just like that export const yourApiName = baseApi.injectEndpoints({ endpoints: (builder) => ({}), });Tuition
thanks, Do you know if there is a way to override more values ? (I cant seem to find it in their docs) like "injecting" a fetchBaseQuery as well? It would be pretty convenient to do this for cases where you want to inject endpoints with different baseUrl.Cornelie
@JoseBernhardt I'm not sure I understand what you mean. But I reckon if you've got 2 separate base URLs it would make sense to have the endpoints for it in separate files. Something like someApi = baseApiMain.injectEndpoints and someOtherApi = baseApiSecondary.injectEndpointsTuition
C
11

instead of baseQuery('token/refresh/', api, extraOptions); you can also do

baseQuery({
  url: 'token/refresh/',
  method: 'POST'
}, api, extraOptions);

The first argument to fetchBaseQuery is just what you would return from the query function in an endpoint definition.

As for your other question, I don't know what exactly you mean by "public" and "private" endpoints. It is your code who calls those queries, so you should know when to call which ones?

Cero answered 29/7, 2021 at 20:14 Comment(2)
Thanks for your reply. What I meant in my other question is how do I manually control whether or not I attach the authentication token? Let's say I have some endpoints that I need to call when I'm authenticated but the endpoints themselves are public (don't require auth token). From my understanding the token will be attached to each and every request if the prepareHeaders method finds it in the storeTuition
I would assume that sending the token just doesn't hurt in those cases. If this is an external service with completely unrelated data (so you don't want to send the token there for privacy reasons), you could also create a seconds api for that.Cero
I
2

I needed to bypass adding the auth token to a refreshToken request recently. I did that by creating an endpoint that used queryFn rather than query:

tokenRefresh: builder.query<TokenRefreshResponse, void>({
    queryFn: async (arg, queryApi, extraOptions, baseQuery) => {
        const response = await fetch(`/api/refresh`);
        return (response.ok) ? {data: await response.json()}
                             : {error: await response.json()};
    }
}),

Ineligible answered 30/9, 2021 at 9:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.