Featured image of post Dealing with refresh tokens in API calls from frontend

Dealing with refresh tokens in API calls from frontend

Web services often provide access token and refresh token pairs for authentication. The access token is used to authenticate requests, while the refresh token is used to obtain a new access token when the current one expires. Since the short-lived access token is always being sent around, not the long-lived refresh token, it’s considered a more secure approach.

However, it can be cumbersome to worry about whether the access token is expired or not in every API call. It’s a good idea to implement a function that handles this for you.

Example

In my SvelteKit application, I have a function called obtain that handles API calls. It checks if the response status is 401 (unauthorized), and if so, it attempts to refresh the token by calling the refreshToken function. If the refresh is successful, it retries the original request.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
async function refreshToken() {
  const response = await fetch("/auth/refresh", { method: "POST" });
  if (response.ok) {
    return true;
  }
  return false;
};

export async function obtain(url?: string, options: RequestInit = {}) {
  if (!url) {
    url = window.location.pathname;
  }

  const defaultHeaders = {
    "Content-Type": "application/json",
    "Accept": "application/json",
  };

  const newOpts: RequestInit = {
    ...options,
    headers: {
      ...defaultHeaders,
      ...options.headers,
    },
  };

  const response = await fetch(url, newOpts);

  if (response.status === 401) {
    const refreshed = await refreshToken();
    if (refreshed) {
      // Retry the original request
      return fetch(url, newOpts);
    } else {
      // If refresh fails logout the user
      goto('/auth/logout');
    }
  }

  return response;
}

Usage: just like a normal fetch call

1
2
3
const response = await obtain("/api/some-endpoint", {
  method: "GET",
});
Note

Here, it is assumed that the refresh token is stored in cookie, so the following happens automatically:

  1. The refresh token is sent to the server
  2. The server checks if the refresh token is valid
  3. If valid, the server sends back a new access token, which is also stored in cookie

Conclusion

In my experience, abstracting away this kind of logic makes an application more readable and maintainable.

The example comes from my Review Planner application, which is currently under development.

Built with Hugo
Theme Stack designed by Jimmy