Skip to content

Commit

Permalink
feat(refresh_token): retrieve/restore refresh_token
Browse files Browse the repository at this point in the history
This enables the use of biometric login
  • Loading branch information
ms-emp committed Nov 15, 2024
1 parent 61bcb8b commit 62ea2db
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 6 deletions.
2 changes: 1 addition & 1 deletion projects/auth-js/oidc/models/args.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type LogoutArgs = MobileWindowParams & PopupWindowParams & RedirectParams
desktopNavigationType?: DesktopNavigation;
};

export type RenewArgs = IFrameWindowParams & ExtraSigninRequestArgs;
export type RenewArgs = IFrameWindowParams & ExtraSigninRequestArgs & { refreshToken?: string };

export type SigninMobileArgs = MobileWindowParams & ExtraSigninRequestArgs;

Expand Down
35 changes: 30 additions & 5 deletions projects/auth-js/oidc/oidc-auth-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { merge } from 'lodash-es';
import {
ErrorResponse, InMemoryWebStorage, Log, SigninSilentArgs, User, UserProfile, WebStorageStateStore
ErrorResponse, IdTokenClaims, InMemoryWebStorage, Log, SigninSilentArgs, User, UserProfile, WebStorageStateStore
} from 'oidc-client-ts';

import { AuthManager, AuthSubscriber, AuthSubscription, AuthSubscriptions, AuthUtils, Optional } from '../core';
Expand Down Expand Up @@ -40,6 +40,7 @@ const DEFAULT_SETTINGS: Optional<OIDCAuthSettings, 'authorityUrl' | 'clientId'>
export class OIDCAuthManager extends AuthManager<OIDCAuthSettings> {
#idTokenSubs = new AuthSubscriptions<[string | undefined]>();
#accessTokenSubs = new AuthSubscriptions<[string | undefined]>();
#refreshTokenSubs = new AuthSubscriptions<[string | undefined]>();
#userProfileSubs = new AuthSubscriptions<[UserProfile | undefined]>();
#userSessionSubs = new AuthSubscriptions<[UserSession | undefined]>();
#authenticatedSubs = new AuthSubscriptions<[boolean]>();
Expand All @@ -49,6 +50,7 @@ export class OIDCAuthManager extends AuthManager<OIDCAuthSettings> {

#idToken?: string;
#accessToken?: string;
#refreshToken?: string;
#userProfile?: UserProfile;
#userSession?: UserSession;
#isAuthenticated = false;
Expand All @@ -62,14 +64,16 @@ export class OIDCAuthManager extends AuthManager<OIDCAuthSettings> {
if (this.#user !== value) {
this.#user = value;

this.#idToken = (value) ? value.id_token : undefined;
this.#accessToken = (value) ? value.access_token : undefined;
this.#userProfile = (value?.profile) ? value.profile : undefined;
this.#userSession = (value) ? UserSession.deserialize(value) : undefined;
this.#idToken = value ? value.id_token : undefined;
this.#accessToken = value ? value.access_token : undefined;
this.#refreshToken = value ? value.refresh_token : undefined;
this.#userProfile = value?.profile ? value.profile : undefined;
this.#userSession = value ? UserSession.deserialize(value) : undefined;
this.#isAuthenticated = !!(value && !value.expired);

this.#idTokenSubs.notify(this.#idToken);
this.#accessTokenSubs.notify(this.#accessToken);
this.#refreshTokenSubs.notify(this.#refreshToken);
this.#userProfileSubs.notify(this.#userProfile);
this.#userSessionSubs.notify(this.#userSession);
this.#authenticatedSubs.notify(this.#isAuthenticated);
Expand Down Expand Up @@ -220,6 +224,17 @@ export class OIDCAuthManager extends AuthManager<OIDCAuthSettings> {
}

public async renew(args?: RenewArgs): Promise<void> {
if (args?.refreshToken) {
await this.#userManager?.storeUser(
new User({
refresh_token: args.refreshToken,
access_token: undefined as unknown as string,
profile: undefined as unknown as IdTokenClaims,
token_type: undefined as unknown as string
})
);
}

return this.#signinSilent(args).catch(error => console.error(error));
}

Expand Down Expand Up @@ -266,11 +281,17 @@ export class OIDCAuthManager extends AuthManager<OIDCAuthSettings> {
return AuthUtils.decodeJwt<AccessToken>(this.#accessToken);
}

public async getRefreshToken(): Promise<string | undefined> {
await this.#waitForRenew('getRefreshToken()');
return this.#refreshToken;
}

// --- DESTROY ---

public destroy(): void {
this.#idTokenSubs.unsubscribe();
this.#accessTokenSubs.unsubscribe();
this.#refreshTokenSubs.unsubscribe();
this.#userProfileSubs.unsubscribe();
this.#userSessionSubs.unsubscribe();
this.#authenticatedSubs.unsubscribe();
Expand All @@ -289,6 +310,10 @@ export class OIDCAuthManager extends AuthManager<OIDCAuthSettings> {
return this.#accessTokenSubs.add(handler);
}

public onRefreshTokenChanged(handler: AuthSubscriber<[string | undefined]>): AuthSubscription {
return this.#refreshTokenSubs.add(handler);
}

public onUserProfileChanged(handler: AuthSubscriber<[UserProfile | undefined]>): AuthSubscription {
return this.#userProfileSubs.add(handler);
}
Expand Down
14 changes: 14 additions & 0 deletions projects/ngx-auth/core/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class AuthService implements OnDestroy {

#idToken$: ReplaySubject<string | undefined> = new ReplaySubject<string | undefined>(1);
#accessToken$: ReplaySubject<string | undefined> = new ReplaySubject<string | undefined>(1);
#refreshToken$: ReplaySubject<string | undefined> = new ReplaySubject<string | undefined>(1);
#userProfile$: ReplaySubject<UserProfile | undefined> = new ReplaySubject<UserProfile | undefined>(1);
#userSession$: ReplaySubject<UserSession | undefined> = new ReplaySubject<UserSession | undefined>(1);
#isAuthenticated$: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
Expand Down Expand Up @@ -77,6 +78,11 @@ export class AuthService implements OnDestroy {
distinctUntilChanged(),
map(token => AuthUtils.decodeJwt<AccessToken>(token))
);

public readonly refreshToken$: Observable<string | undefined> =
this.#refreshToken$.asObservable().pipe(
distinctUntilChanged()
);
/* eslint-enable @typescript-eslint/member-ordering */

// --- OIDCAuthManager ---
Expand Down Expand Up @@ -165,12 +171,20 @@ export class AuthService implements OnDestroy {
return this.#manager.getAccessTokenDecoded();
}

/**
* @see {@link OIDCAuthManager.getRefreshToken}
*/
public async getRefreshToken(): Promise<string | undefined> {
return this.#manager.getRefreshToken();
}

// --- HELPER(s) ----

#listenForManagerChanges(): void {
this.#authManagerSubs.push(
this.#manager.onIdTokenChanged(value => this.#ngZone.run(() => this.#idToken$.next(value))),
this.#manager.onAccessTokenChanged(value => this.#ngZone.run(() => this.#accessToken$.next(value))),
this.#manager.onRefreshTokenChanged(value => this.#ngZone.run(() => this.#refreshToken$.next(value))),
this.#manager.onUserProfileChanged(value => this.#ngZone.run(() => this.#userProfile$.next(value))),
this.#manager.onUserSessionChanged(value => this.#ngZone.run(() => this.#userSession$.next(value))),
this.#manager.onAuthenticatedChanged(value => this.#ngZone.run(() => this.#isAuthenticated$.next(value))),
Expand Down

0 comments on commit 62ea2db

Please sign in to comment.