Save existing refresh_token in store if no new refresh_token is returned (#483)

As described in https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12#name-refresh-token-response,
server MAY return a new refresh_token. In case it doesn't, current implementation discard initial refresh
token if it was still valid.
This commit is contained in:
Frédéric Barthelet 2025-05-21 09:29:56 -07:00 • committed by GitHub
commit 281cf0bf8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 22 additions and 3 deletions

View file

@ -506,6 +506,9 @@ describe("OAuth Authorization", () => {
access_token: "newaccess123",
token_type: "Bearer",
expires_in: 3600,
}
const validTokensWithNewRefreshToken = {
...validTokens,
refresh_token: "newrefresh123",
};
@ -520,7 +523,7 @@ describe("OAuth Authorization", () => {
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => validTokens,
json: async () => validTokensWithNewRefreshToken,
});
const tokens = await refreshAuthorization("https://auth.example.com", {
@ -528,7 +531,7 @@ describe("OAuth Authorization", () => {
refreshToken: "refresh123",
});
expect(tokens).toEqual(validTokens);
expect(tokens).toEqual(validTokensWithNewRefreshToken);
expect(mockFetch).toHaveBeenCalledWith(
expect.objectContaining({
href: "https://auth.example.com/token",
@ -548,6 +551,22 @@ describe("OAuth Authorization", () => {
expect(body.get("client_secret")).toBe("secret123");
});
it("exchanges refresh token for new tokens and keep existing refresh token if none is returned", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => validTokens,
});
const refreshToken = "refresh123";
const tokens = await refreshAuthorization("https://auth.example.com", {
clientInformation: validClientInfo,
refreshToken,
});
expect(tokens).toEqual({ refresh_token: refreshToken, ...validTokens });
});
it("validates token response schema", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,

View file

@ -474,7 +474,7 @@ export async function refreshAuthorization(
throw new Error(`Token refresh failed: HTTP ${response.status}`);
}
return OAuthTokensSchema.parse(await response.json());
return OAuthTokensSchema.parse({ refresh_token: refreshToken, ...(await response.json()) });
}
/**