AzureAD / microsoft-authentication-library-for-js

Microsoft Authentication Library (MSAL) for JS
http://aka.ms/aadv2
MIT License
3.64k stars 2.65k forks source link

Insufficient permission and empty scope when user logs in and grants admin consent #3524

Closed yousourceinc closed 3 years ago

yousourceinc commented 3 years ago

Core Library

@azure/msal or msal

Core Library Version

1.4.8

Wrapper Library

@azure/msal-angular

Wrapper Library Version

1.1.2

Description

We are receiving an invalid accessToken with empty scopes on the first grant of admin consent. If the user retries again - we call the acquireTokenPopup and the accessToken becomes valid. We can't call acquireTokenPopup when the accessToken is invalid since it results in a popup_window_error

Once the user grants the admin consent, we will request for all users in their AD. (User.Read.All)

If the current user is not an admin, and the admin consent was granted by another person via an admin consent link, the user does not have any issue accessing the User.Read.All endpoints on the first login.

Error Message

Insufficient privileges to complete the operation.

Msal Logs

No response

MSAL Configuration

MsalModule.forRoot({
    auth: {
      clientId: environment.microsoftAppID,
      validateAuthority: true,
      redirectUri: environment.microsoftRedirectUri,
      postLogoutRedirectUri: `${environment.webUrl}/logout`,
      navigateToLoginRequestUrl: false
    },
    cache: {
      cacheLocation: 'localStorage',
      storeAuthStateInCookie: isIE // set to true for IE 11
    }
  },
    {
      popUp: !isIE,
      unprotectedResources: ['https://www.microsoft.com/en-us/'],
      extraQueryParameters: {}
    }
  ),

Relevant Code Snippets

getAccessToken(returnRaw: boolean = false): Promise<void | string> {
    const scopes: Array<string> = this.isAdmin ? environment.microsoftAdminScopes : environment.microsoftScopes;
    const account: Account = this.getAccount();
    const request: AuthenticationParameters = {
      scopes,
      redirectUri: '',
      authority: `https://login.microsoftonline.com/${!!this.microsoftTenantId ? this.microsoftTenantId : 'common'}`,
      forceRefresh: true
    };

    if (!!account) {
      // request.account = account;
      request.loginHint = account.userName;
    }

    return this.msalService.acquireTokenSilent(request).then((result: AuthResponse) => {
      this.validateScope(result);
      this.storeCurrentAuthentication(result);
      return returnRaw ? result.idToken.rawIdToken : result.accessToken;
    }).catch((error: ClientAuthError | InteractionRequiredAuthError) => {
      this.logger(JSON.stringify(error));
      if (error instanceof InteractionRequiredAuthError
        || error.errorCode === 'null_or_empty_id_token'
        || error.errorCode === 'token_renewal_error'
        || error.errorCode === 'login_required'
        || error.errorCode === 'consent_required') {
        return this.msalService.acquireTokenPopup(request)
................

// Prompt the user to sign in and
  // grant consent to the requested permission scopes
  private async signIn(email: string, scopes: Array<string>, tenantId?: Guid,
    microsoftTenantId?: string, redirect: boolean = false): Promise<AuthResponse | void> {
    const request: AuthenticationParameters = this.generateRequest(scopes, email, microsoftTenantId);

    if (this.checkIfRedirect(redirect)) {
      return this.redirectSignIn(request, tenantId);
    }

    let result: AuthResponse = null;
    if (!this.authenticated) {
      result = await this.msalService.loginPopup(request);
    } else {
      result = await this.msalService.acquireTokenPopup(request);
    }
.............................

private generateRequest(scopes: Array<string>, email: string, microsoftTenantId: string): AuthenticationParameters {
    const request: AuthenticationParameters = {
      scopes,
      loginHint: email,
      authority: this.getAuthority(microsoftTenantId),
      forceRefresh: true,
      redirectUri: '',
      extraQueryParameters: {
        hsu: '1'
      }
    };

    if (!this.authenticated) {
      request.prompt = 'login';
    }

    if (this.requestConsent) {
      request.prompt = 'consent';
      this.requestConsent = false;
    }

    return request;
  }

  private getAuthority(microsoftTenantId: string): string {
    return `https://login.microsoftonline.com/${!!microsoftTenantId ? microsoftTenantId : 'common'}`;
  }
..................

Reproduction Steps

Step 1: Admin clicks the log in button (loginPopup) Step 2: MS prompts login and admin consent page (We need User.Read.All) Step 3: We call acquireTokenSilent() to acquire for Access Token as the AuthenticationProvider of our Graph Client image Step 4: Graph API /users request returns 403 - insufficient permission image

Expected Behavior

Received access token already has the correct rights/permission at least at the getAccessToken() endpoint.

Identity Provider

Azure AD / MSA

Browsers Affected (Select all that apply)

Chrome

Regression

No response

Source

Internal (Microsoft)

hectormmg commented 3 years ago

Hi @yousourceinc . Could you send me a network trace from a tool like Fiddler so we can inspect the requests your app is making and have more context on your issue? You can send it to the e-mail on my profile.

Also, please consider upgrading to @azure/msal-angular v2, which does a better job of handling this type of scenario. Thanks!

ghost commented 3 years ago

yousourceinc This issue has been automatically marked as stale because it is marked as requiring author feedback but has not had any activity for 5 days. If your issue has not been resolved please leave a comment to keep this open. It will be closed in 7 days if it remains stale.