diff --git a/docs/oidc-client-ts.api.md b/docs/oidc-client-ts.api.md index ff727000a..b3d2b7b46 100644 --- a/docs/oidc-client-ts.api.md +++ b/docs/oidc-client-ts.api.md @@ -91,7 +91,7 @@ export class ErrorTimeout extends Error { export type ExtraHeader = string | (() => string); // @public (undocumented) -export type ExtraSigninRequestArgs = Pick; +export type ExtraSigninRequestArgs = Pick; // @public (undocumented) export type ExtraSignoutRequestArgs = Pick; diff --git a/src/OidcClient.test.ts b/src/OidcClient.test.ts index 04fed8610..68b35497a 100644 --- a/src/OidcClient.test.ts +++ b/src/OidcClient.test.ts @@ -422,7 +422,7 @@ describe("OidcClient", () => { session_state: "session_state", scope: "openid", profile: {} as UserProfile, - }); + }, "resource"); // act const response = await subject.useRefreshToken({ state }); @@ -432,6 +432,7 @@ describe("OidcClient", () => { refresh_token: "refresh_token", scope: "openid", timeoutInSeconds: undefined, + resource: "resource", }); expect(response).toBeInstanceOf(SigninResponse); expect(response).toMatchObject(tokenResponse); diff --git a/src/OidcClient.ts b/src/OidcClient.ts index 5185c03b0..b4f341664 100644 --- a/src/OidcClient.ts +++ b/src/OidcClient.ts @@ -201,6 +201,7 @@ export class OidcClient { const result = await this._tokenClient.exchangeRefreshToken({ refresh_token: state.refresh_token, + resource: state.resource, // provide the (possible filtered) scope list scope, timeoutInSeconds, diff --git a/src/RefreshState.ts b/src/RefreshState.ts index f02f2cbed..66f78293c 100644 --- a/src/RefreshState.ts +++ b/src/RefreshState.ts @@ -17,6 +17,7 @@ export class RefreshState { public readonly session_state: string | null; public readonly scope?: string; public readonly profile: UserProfile; + public readonly resource?: string | string[]; constructor(args: { refresh_token: string; @@ -26,13 +27,15 @@ export class RefreshState { profile: UserProfile; state?: unknown; - }) { + }, resource?: string | string[]) { this.refresh_token = args.refresh_token; this.id_token = args.id_token; this.session_state = args.session_state; this.scope = args.scope; this.profile = args.profile; + this.resource = resource; this.data = args.state; + } } diff --git a/src/TokenClient.ts b/src/TokenClient.ts index e39bab8bf..6d6f5ac47 100644 --- a/src/TokenClient.ts +++ b/src/TokenClient.ts @@ -43,6 +43,7 @@ export interface ExchangeRefreshTokenArgs { grant_type?: string; refresh_token: string; scope?: string; + resource?: string | string[]; timeoutInSeconds?: number; } @@ -201,7 +202,10 @@ export class TokenClient { const params = new URLSearchParams({ grant_type }); for (const [key, value] of Object.entries(args)) { - if (value != null) { + if (Array.isArray(value)) { + value.forEach(param => params.append(key, param)); + } + else if (value != null) { params.set(key, value); } } diff --git a/src/UserManager.test.ts b/src/UserManager.test.ts index 022db77fc..2416c655b 100644 --- a/src/UserManager.test.ts +++ b/src/UserManager.test.ts @@ -505,6 +505,41 @@ describe("UserManager", () => { }), ); }); + + it("should use the resource from settings when a refresh token is present", async () => { + // arrange + const user = new User({ + access_token: "access_token", + token_type: "token_type", + refresh_token: "refresh_token", + profile: { + sub: "sub", + nickname: "Nick", + } as UserProfile, + }); + + const useRefreshTokenSpy = jest.spyOn(subject["_client"], "useRefreshToken").mockResolvedValue({ + access_token: "new_access_token", + profile: { + sub: "sub", + nickname: "Nicholas", + }, + } as unknown as SigninResponse); + subject["_loadUser"] = jest.fn().mockResolvedValue(user); + + // act + await subject.signinSilent({ resource: "resource" }); + expect(useRefreshTokenSpy).toBeCalledWith( + expect.objectContaining({ + state: { + resource: "resource", + refresh_token: user.refresh_token, + session_state: null, + "profile": { "nickname": "Nick", "sub": "sub" }, + }, + }), + ); + }); }); describe("signinSilentCallback", () => { diff --git a/src/UserManager.ts b/src/UserManager.ts index 5fee81c86..810c6f8ca 100644 --- a/src/UserManager.ts +++ b/src/UserManager.ts @@ -20,7 +20,7 @@ import type { SigninResponse } from "./SigninResponse"; /** * @public */ -export type ExtraSigninRequestArgs = Pick; +export type ExtraSigninRequestArgs = Pick; /** * @public */ @@ -256,13 +256,14 @@ export class UserManager { const logger = this._logger.create("signinSilent"); const { silentRequestTimeoutInSeconds, + resource, ...requestArgs } = args; // first determine if we have a refresh token, or need to use iframe let user = await this._loadUser(); if (user?.refresh_token) { logger.debug("using refresh token"); - const state = new RefreshState(user as Required); + const state = new RefreshState(user as Required, resource); return await this._useRefreshToken(state); }