Skip to content

Commit 32b8edf

Browse files
benMainpamapa
authored andcommitted
feat(extraTokenProperties): Add extra token properties
1 parent ea395bf commit 32b8edf

6 files changed

Lines changed: 72 additions & 4 deletions

File tree

docs/oidc-client-ts.api.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,12 +969,13 @@ export class User {
969969
expires_at?: number;
970970
userState?: unknown;
971971
url_state?: string;
972-
});
972+
}, extraTokenResponseKeys?: string[]);
973973
access_token: string;
974974
get expired(): boolean | undefined;
975975
expires_at?: number;
976976
get expires_in(): number | undefined;
977977
set expires_in(value: number | undefined);
978+
extraTokenResponseProperties?: Record<string, unknown>;
978979
// (undocumented)
979980
static fromStorageString(storageString: string): User;
980981
id_token?: string;
@@ -1122,6 +1123,7 @@ export interface UserManagerSettings extends OidcClientSettings {
11221123
accessTokenExpiringNotificationTimeInSeconds?: number;
11231124
automaticSilentRenew?: boolean;
11241125
checkSessionIntervalInSeconds?: number;
1126+
extraTokenResponseKeys?: string[];
11251127
iframeAttributes?: Record<string, string> | undefined;
11261128
iframeNotifyParentOrigin?: string;
11271129
iframeScriptOrigin?: string;
@@ -1160,6 +1162,8 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore {
11601162
// (undocumented)
11611163
readonly checkSessionIntervalInSeconds: number;
11621164
// (undocumented)
1165+
readonly extraTokenResponseKeys: string[];
1166+
// (undocumented)
11631167
readonly iframeAttributes?: Record<string, string>;
11641168
// (undocumented)
11651169
readonly iframeNotifyParentOrigin: string | undefined;

src/User.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,21 @@ describe("User", () => {
4646
expect(subject.scopes).toEqual(["foo", "bar", "baz"]);
4747
});
4848
});
49+
50+
describe("extraTokenResponseProperties", () => {
51+
it("should not provide extraTokenResponseProperties if extraTokenResponseKeys is not provided", () => {
52+
const subject = new User({} as never);
53+
expect(subject.extraTokenResponseProperties).not.toBeDefined();
54+
});
55+
it("should provide extraTokenResponseProperties if extraTokenResponseKeys is provided", () => {
56+
const patient = "12345";
57+
const subject = new User({ access_token: "notAToken", id_token: "not", patient, token_type: "Bearer" } as never, ["patient"]);
58+
const actualTokenProps = subject.extraTokenResponseProperties ?? {};
59+
const keys = Object.keys(actualTokenProps);
60+
const values = Object.values(actualTokenProps);
61+
expect(subject.extraTokenResponseProperties).toBeDefined();
62+
expect(keys).toContain("patient");
63+
expect(values).toContain(patient);
64+
});
65+
});
4966
});

src/User.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ export class User {
4949
/** The expires at returned from the OIDC provider. */
5050
public expires_at?: number;
5151

52+
/**
53+
* The additional token response properties the user has requested to keep via settings.
54+
* @see {@link UserManagerSettings.extraTokenResponseKeys}
55+
*/
56+
public extraTokenResponseProperties?: Record<string, unknown>;
57+
5258
/** custom state data set during the initial signin request */
5359
public readonly state: unknown;
5460
public readonly url_state?: string;
@@ -64,7 +70,7 @@ export class User {
6470
expires_at?: number;
6571
userState?: unknown;
6672
url_state?: string;
67-
}) {
73+
}, extraTokenResponseKeys?: string[]) {
6874
this.id_token = args.id_token;
6975
this.session_state = args.session_state ?? null;
7076
this.access_token = args.access_token;
@@ -76,6 +82,16 @@ export class User {
7682
this.expires_at = args.expires_at;
7783
this.state = args.userState;
7884
this.url_state = args.url_state;
85+
86+
if (extraTokenResponseKeys?.length) {
87+
this.extraTokenResponseProperties = {};
88+
for (const key of extraTokenResponseKeys) {
89+
const extraToken = (args as Record<string, unknown>)[key];
90+
if (extraToken) {
91+
this.extraTokenResponseProperties[key] = extraToken;
92+
}
93+
}
94+
}
7995
}
8096

8197
/** Computed number of seconds the access token has remaining. */

src/UserManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ export class UserManager {
548548

549549
protected async _buildUser(signinResponse: SigninResponse, verifySub?: string) {
550550
const logger = this._logger.create("_buildUser");
551-
const user = new User(signinResponse);
551+
const user = new User(signinResponse, this.settings.extraTokenResponseKeys);
552552
if (verifySub) {
553553
if (verifySub !== user.profile.sub) {
554554
logger.debug("current user does not match user returned from signin. sub from signin:", user.profile.sub);

src/UserManagerSettings.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,4 +421,24 @@ describe("UserManagerSettings", () => {
421421
expect(subject.silentRequestTimeoutInSeconds).toEqual(10);
422422
});
423423
});
424+
425+
describe("extraTokenResponseKeys", () => {
426+
it("should return value from the initial settings", () => {
427+
const subject = new UserManagerSettingsStore({
428+
authority: "authority",
429+
client_id: "client",
430+
redirect_uri: "redirect",
431+
extraTokenResponseKeys: ["testProp"],
432+
});
433+
expect(subject.extraTokenResponseKeys).toEqual(["testProp"]);
434+
});
435+
it("should return the default value", () => {
436+
const subject = new UserManagerSettingsStore({
437+
authority: "authority",
438+
client_id: "client",
439+
redirect_uri: "redirect",
440+
});
441+
expect(subject.extraTokenResponseKeys).toEqual([]);
442+
});
443+
});
424444
});

src/UserManagerSettings.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ export interface UserManagerSettings extends OidcClientSettings {
9292
*/
9393
maxSilentRenewTimeoutRetries?: number;
9494

95+
/**
96+
* By default the user object only preserves the standard properties
97+
* (access_token, session_state, id_token, refresh_token, token_type, scope, profile, and expiration).
98+
* Any additional properties returned by the OIDC/OAuth2 token response will be ignored unless explicitly listed here. (default: [])
99+
*/
100+
extraTokenResponseKeys?: string[];
101+
95102
/**
96103
* Storage object used to persist User for currently authenticated user (default: window.sessionStorage, InMemoryWebStorage iff no window).
97104
* E.g. `userStore: new WebStorageStateStore({ store: window.localStorage })`
@@ -136,6 +143,8 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore {
136143
public readonly accessTokenExpiringNotificationTimeInSeconds: number;
137144
public readonly maxSilentRenewTimeoutRetries?: number;
138145

146+
public readonly extraTokenResponseKeys: string[];
147+
139148
public readonly userStore: StateStore;
140149

141150
public constructor(args: UserManagerSettings) {
@@ -169,9 +178,10 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore {
169178
includeIdTokenInSilentSignout = false,
170179

171180
accessTokenExpiringNotificationTimeInSeconds = DefaultAccessTokenExpiringNotificationTimeInSeconds,
172-
173181
maxSilentRenewTimeoutRetries,
174182

183+
extraTokenResponseKeys = [],
184+
175185
userStore,
176186
} = args;
177187

@@ -206,6 +216,7 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore {
206216

207217
this.accessTokenExpiringNotificationTimeInSeconds = accessTokenExpiringNotificationTimeInSeconds;
208218
this.maxSilentRenewTimeoutRetries = maxSilentRenewTimeoutRetries;
219+
this.extraTokenResponseKeys = extraTokenResponseKeys;
209220

210221
if (userStore) {
211222
this.userStore = userStore;

0 commit comments

Comments
 (0)