Skip to content

Commit 6150dd5

Browse files
authored
fix: force interactive token refresh after permission revocation
* fix: force interactive token refresh after permission revocation
1 parent c06feea commit 6150dd5

File tree

3 files changed

+44
-6
lines changed

3 files changed

+44
-6
lines changed

src/app/services/actions/revoke-scopes.action.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { createAsyncThunk } from '@reduxjs/toolkit';
22
import { ApplicationState } from '../../../store';
3+
import { authenticationWrapper } from '../../../modules/authentication';
34
import { componentNames, eventTypes, telemetry } from '../../../telemetry';
45
import { IOAuthGrantPayload } from '../../../types/permissions';
56
import { IUser } from '../../../types/profile';
67
import { RevokeScopesError } from '../../utils/error-utils/RevokeScopesError';
78
import { translateMessage } from '../../utils/translate-messages';
89
import { DEFAULT_USER_SCOPES, REVOKING_PERMISSIONS_REQUIRED_SCOPES } from '../graph-constants';
9-
import { getConsentedScopesSuccess } from '../slices/auth.slice';
10+
import { getAuthTokenSuccess, getConsentedScopesSuccess } from '../slices/auth.slice';
11+
import { fetchAllPrincipalGrants } from '../slices/permission-grants.slice';
1012
import { setQueryResponseStatus } from '../slices/query-status.slice';
1113
import { REVOKE_STATUS, RevokePermissionsUtil } from './permissions-action-creator.util';
1214

@@ -62,8 +64,21 @@ export const revokeScopes = createAsyncThunk(
6264
const updatedScopes = await updatePermissions(permissionsUpdateObject);
6365

6466
if (updatedScopes) {
67+
// Force token refresh with user interaction to get new token with updated scopes
68+
try {
69+
const authResponse = await authenticationWrapper.refreshToken(updatedScopes);
70+
71+
if (authResponse && authResponse.accessToken) {
72+
dispatch(getAuthTokenSuccess());
73+
}
74+
} catch (error) {
75+
// Even if refresh fails, we still update the consented scopes
76+
// Token will be refreshed on next API call
77+
}
78+
6579
dispatchScopesStatus(dispatch, 'Permission revoked', 'Success', 'success');
6680
dispatch(getConsentedScopesSuccess(updatedScopes));
81+
dispatch(fetchAllPrincipalGrants());
6782
trackRevokeConsentEvent(REVOKE_STATUS.success, permissionToRevoke);
6883
return updatedScopes;
6984
} else {

src/messages/GE.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@
457457
"No samples found": "We did not find any sample queries",
458458
"You require the following permissions to revoke": "You require Directory.Read.All and DelegatedPermissionGrant.ReadWrite.All to be able to revoke consent to permissions",
459459
"Cannot delete default scope": "Graph Explorer requires this permission for its normal working behavior",
460-
"Permission revoked": "Permission revoked successfully. Sign out and sign in again for these changes to take effect. These changes may take some time to reflect on your token",
460+
"Permission revoked": "Permission revoked successfully. If you don’t see the change right away, sign out and back in. The update may take some time to propagate to your token.",
461461
"An error occurred when unconsenting": "An error occurred when unconsenting. Please try again",
462462
"You are unconsenting to an admin pre-consented permission": "You are unconsenting to an admin pre-consented permission. Ask your tenant admin to revoke consent to this permission on Azure AD",
463463
"Possible error found in URL near": "Possible error found in URL near",

src/modules/authentication/AuthenticationWrapper.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const defaultScopes = DEFAULT_USER_SCOPES.split(' ');
2626
export class AuthenticationWrapper implements IAuthenticationWrapper {
2727
private static instance: AuthenticationWrapper;
2828
private consentingToNewScopes: boolean = false;
29+
private revokingScopes: boolean = false;
2930
private performingStepUpAuth: boolean = false;
3031
private claimsAvailable: boolean = false;
3132
private sampleQuery: IQuery = {
@@ -117,11 +118,32 @@ export class AuthenticationWrapper implements IAuthenticationWrapper {
117118
*/
118119
public async consentToScopes(scopes: string[] = []): Promise<AuthenticationResult> {
119120
this.consentingToNewScopes = true;
120-
// eslint-disable-next-line no-useless-catch
121121
try {
122-
return await this.loginWithInteraction(scopes);
122+
const result = await this.loginWithInteraction(scopes);
123+
this.consentingToNewScopes = false;
124+
return result;
125+
} catch (error) {
126+
this.consentingToNewScopes = false;
127+
throw error;
128+
}
129+
}
130+
131+
/**
132+
* Forces a token refresh with user interaction to ensure token reflects updated scopes
133+
* This is necessary after permission changes (consent/revoke) to get a fresh token from Azure AD
134+
* @param {string[]} scopes - optional scopes to refresh token with
135+
* @returns {Promise.<AuthenticationResult>}
136+
*/
137+
public async refreshToken(scopes: string[] = []): Promise<AuthenticationResult> {
138+
this.revokingScopes = true;
139+
140+
try {
141+
const result = await this.loginWithInteraction(scopes.length > 0 ? scopes : defaultScopes);
142+
this.revokingScopes = false;
143+
return result;
123144
} catch (error) {
124-
throw new Error(`Error occurred while consenting to scopes: ${error}`);
145+
this.revokingScopes = false;
146+
throw error;
125147
}
126148
}
127149

@@ -255,7 +277,7 @@ export class AuthenticationWrapper implements IAuthenticationWrapper {
255277
tokenQueryParameters: getExtraQueryParameters()
256278
};
257279

258-
if (this.consentingToNewScopes || this.performingStepUpAuth) {
280+
if (this.consentingToNewScopes || this.performingStepUpAuth || this.revokingScopes) {
259281
delete popUpRequest.prompt;
260282
popUpRequest.loginHint = this.getAccount()?.username;
261283
}
@@ -295,6 +317,7 @@ export class AuthenticationWrapper implements IAuthenticationWrapper {
295317
localStorage.removeItem(HOME_ACCOUNT_KEY);
296318
}
297319

320+
298321
/**
299322
* This is an own implementation of the clearCache() function that is no longer available;
300323
* to support logging out via Popup which is not currently native to the msal application.

0 commit comments

Comments
 (0)