Skip to content

Commit

Permalink
Merge pull request #1640 from solliancenet/sc-silent-token-refresh-ui…
Browse files Browse the repository at this point in the history
…-081

(0.8.1) Fix silent token refresh in UI's
  • Loading branch information
ciprianjichici authored Aug 28, 2024
2 parents 7d381e3 + e21d720 commit b1b2a96
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 370 deletions.
17 changes: 11 additions & 6 deletions src/ui/ManagementPortal/js/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,18 @@ export default {
this.instanceId = instanceId;
},

bearerToken: null,
/**
* Retrieves the bearer token for authentication.
* If the bearer token is already available, it will be returned immediately.
* Otherwise, it will acquire a new bearer token using the MSAL instance.
* @returns The bearer token.
*/
async getBearerToken() {
if (this.bearerToken) return this.bearerToken;

const token = await useNuxtApp().$authStore.getToken();
this.bearerToken = token.accessToken;
return this.bearerToken;
// When the scope is specific on aquireTokenSilent this seems to be instant
// otherwise we would have to store the token and check if it has expired here
// to determine if we need to fetch it again
const token = await useNuxtApp().$authStore.getApiToken();
return token.accessToken;
},

async fetch(url: string, opts: any = {}) {
Expand Down
137 changes: 0 additions & 137 deletions src/ui/ManagementPortal/js/auth.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/ui/ManagementPortal/stores/appConfigStore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { defineStore } from 'pinia';
// import type { AuthConfigOptions } from '@js/auth';
import api from '@/js/api';

export const useAppConfigStore = defineStore('appConfig', {
Expand Down Expand Up @@ -44,7 +43,7 @@ export const useAppConfigStore = defineStore('appConfig', {
tenantId: null,
scopes: [],
callbackPath: null,
}, // as AuthConfigOptions,
},
}),
getters: {},
actions: {
Expand Down
68 changes: 28 additions & 40 deletions src/ui/ManagementPortal/stores/authStore.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { defineStore } from 'pinia';
import type { AccountInfo } from '@azure/msal-browser';
import { PublicClientApplication, EventType } from '@azure/msal-browser';
import { PublicClientApplication } from '@azure/msal-browser';

export const useAuthStore = defineStore('auth', {
state: () => ({
msalInstance: null,
tokenExpirationTimerId: null as number | null,
isExpired: false,
apiToken: null,
}),

getters: {
Expand All @@ -25,6 +26,10 @@ export const useAuthStore = defineStore('auth', {
authConfig() {
return useNuxtApp().$appConfigStore.auth;
},

apiScopes() {
return [this.authConfig.scopes];
},
},

actions: {
Expand All @@ -34,7 +39,7 @@ export const useAuthStore = defineStore('auth', {
clientId: this.authConfig.clientId,
authority: `${this.authConfig.instance}${this.authConfig.tenantId}`,
redirectUri: this.authConfig.callbackPath,
scopes: this.authConfig.scopes,
scopes: this.apiScopes,
// Must be registered as a SPA redirectURI on your app registration.
postLogoutRedirectUri: '/',
},
Expand All @@ -43,72 +48,55 @@ export const useAuthStore = defineStore('auth', {
},
});

msalInstance.addEventCallback((event) => {
const { eventType } = event;
if (
eventType === EventType.ACQUIRE_TOKEN_SUCCESS ||
eventType === EventType.LOGIN_SUCCESS
) {
this.createTokenRefreshTimer();
}
});

await msalInstance.initialize();

this.msalInstance = msalInstance;

return this;
},

createTokenRefreshTimer() {
const tokenExpirationTime = this.currentAccount.idTokenClaims.exp * 1000;
const tokenExpirationTimeMS = this.apiToken.expiresOn;
const currentTime = Date.now();
const timeUntilExpirationMS = tokenExpirationTime - currentTime;
const timeUntilExpirationMS = tokenExpirationTimeMS - currentTime;

if (timeUntilExpirationMS <= 0) {
console.log(`Auth: Access token expired ${timeUntilExpirationMS / 1000} seconds ago.`);
this.isExpired = true;
return;
// return useNuxtApp().$router.push({
// name: 'auth/login',
// query: {
// message: 'Your login has expired. Please sign in again.',
// },
// });
// If the token expires within the next minute, try to refresh it
if (timeUntilExpirationMS <= 60 * 1000) {
return this.tryTokenRefresh();
}

console.log(`Auth: Cleared previous access token timer.`);
clearTimeout(this.tokenExpirationTimerId);

this.tokenExpirationTimerId = setTimeout(() => {
this.refreshToken();
this.tryTokenRefresh();
}, timeUntilExpirationMS);

const refreshDate = new Date(tokenExpirationTimeMS);
console.log(
`Auth: Set access token timer refresh in ${timeUntilExpirationMS / 1000} seconds.`,
`Auth: Set access token timer refresh for ${refreshDate} (in ${timeUntilExpirationMS / 1000} seconds).`,
);
},

async refreshToken() {
async tryTokenRefresh() {
try {
await this.msalInstance.acquireTokenSilent({
account: this.currentAccount,
scopes: [this.authConfig.scopes],
});
console.log('Auth: Refreshed access token.');
this.createTokenRefreshTimer();
await this.getApiToken();
console.log('Auth: Successfully refreshed access token.');
} catch (error) {
console.error('Auth: Token refresh error:', error);
// sessionStorage.clear();
console.error('Auth: Failed to refresh access token:', error);
this.isExpired = true;
// useNuxtApp().$router.push({ name: 'auth/login' });
}
},

async getToken() {
async getApiToken() {
try {
return await this.msalInstance.acquireTokenSilent({
this.apiToken = await this.msalInstance.acquireTokenSilent({
account: this.currentAccount,
scopes: this.apiScopes,
});

this.createTokenRefreshTimer();

return this.apiToken;
} catch (error) {
this.isExpired = true;
throw error;
Expand All @@ -117,7 +105,7 @@ export const useAuthStore = defineStore('auth', {

async login() {
return await this.msalInstance.loginRedirect({
scopes: [this.authConfig.scopes],
scopes: this.apiScopes,
});
},

Expand Down
11 changes: 5 additions & 6 deletions src/ui/UserPortal/js/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type {

export default {
apiUrl: null as string | null,
bearerToken: null as string | null,
virtualUser: null as string | null,

getVirtualUser() {
Expand Down Expand Up @@ -47,11 +46,11 @@ export default {
* @returns The bearer token.
*/
async getBearerToken() {
if (this.bearerToken) return this.bearerToken;

const token = await useNuxtApp().$authStore.getToken();
this.bearerToken = token.accessToken;
return this.bearerToken;
// When the scope is specific on aquireTokenSilent this seems to be instant
// otherwise we would have to store the token and check if it has expired here
// to determine if we need to fetch it again
const token = await useNuxtApp().$authStore.getApiToken();
return token.accessToken;
},

/**
Expand Down
Loading

0 comments on commit b1b2a96

Please sign in to comment.