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.

Oleksandr Fomin
  • 2,005
  • 5
  • 25
  • 47
  • It'd be also nice to know how to separate requests to private and public endpoints – Oleksandr Fomin Jul 29 '21 at 19:04
  • 1
    I'm curious to know how do you make all the other api slices extend from this `baseQueryWithReauth` – Jose Bernhardt Oct 24 '21 at 04:38
  • 1
    @Jose Bernhardt Just like that `export const yourApiName = baseApi.injectEndpoints({ endpoints: (builder) => ({}), });` – Oleksandr Fomin Oct 25 '21 at 08:47
  • 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`. – Jose Bernhardt Oct 25 '21 at 18:18
  • @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.injectEndpoints` – Oleksandr Fomin Oct 26 '21 at 06:34

2 Answers2

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?

phry
  • 35,762
  • 5
  • 67
  • 81
  • 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 store – Oleksandr Fomin Jul 29 '21 at 20:36
  • 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. – phry Jul 30 '21 at 07:04
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()};
    }
}),

Ben Golding
  • 726
  • 6
  • 8