Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angular 16: BrowserAuthError: uninitialized_public_client_application: You must call and await the initialize function before attempting to call any other MSAL API. #6693

Closed
Lakshman-Sundeep-Teki opened this issue Nov 13, 2023 · 20 comments
Labels
bug-unconfirmed A reported bug that needs to be investigated and confirmed msal-angular Related to @azure/msal-angular package msal-browser Related to msal-browser package Needs: Author Feedback Awaiting response from issue author no-issue-activity Issue author has not responded in 5 days public-client Issues regarding PublicClientApplications question Customer is asking for a clarification, use case or information.

Comments

@Lakshman-Sundeep-Teki
Copy link

Lakshman-Sundeep-Teki commented Nov 13, 2023

Core Library

MSAL.js (@azure/msal-browser)

Core Library Version

3.0.8

Wrapper Library

MSAL Angular (@azure/msal-angular)

Wrapper Library Version

None

Public or Confidential Client?

Public

Description

I am trying to authenticate user during the page load it self because, i want to authenticate user with out any button click. To achieve that i was trying to use loginRedirect in onInit life cycle hook of app.component.ts file, When i was trying to do like that i am facing an issue

BrowserAuthError: uninitialized_public_client_application: You must call and await the initialize function before attempting to call any other MSAL API.

Is there any other way to authenticate user with out any button click

Error Message

BrowserAuthError: uninitialized_public_client_application: You must call and await the initialize function before attempting to call any other MSAL API. For more visit: aka.ms/msaljs/browser-errors
BrowserAuthError: uninitialized_public_client_application: You must call and await the initialize function before attempting to call any other MSAL API. For more visit: aka.ms/msaljs/browser-errors

Msal Logs

No response

MSAL Configuration

{
auth: {
      clientId: environment.msalConfig.auth.clientId,
      authority: environment.msalConfig.auth.authority,
      redirectUri: window.location.origin,
      postLogoutRedirectUri: '/'
    },
    cache: {
      cacheLocation: BrowserCacheLocation.LocalStorage
    },
}

Relevant Code Snippets

app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, AfterViewInit, OnDestroy{
  title = 'sso-poc';
  loginDisplay = false;
  private readonly _destroying$ = new Subject<void>();
  constructor(private msalBroadcastService: MsalBroadcastService ,private authService: MsalService) {}
  ngOnDestroy(): void {
    this._destroying$.next(undefined);
    this._destroying$.complete();
  }
  ngAfterViewInit(): void {
   
  }
  //AD auth
  ngOnInit(): void {
    this.authService.loginRedirect();  
   
      this.msalBroadcastService.inProgress$
      .pipe(
        filter((status: InteractionStatus) => status === InteractionStatus.None),
        takeUntil(this._destroying$)
      )
      .subscribe(() => {
        this.setLoginDisplay();
        this.checkAndSetActiveAccount();
      })
  }

  checkAndSetActiveAccount(){
    /**
     * If no active account set but there are accounts signed in, sets first account to active account
     * To use active account set here, subscribe to inProgress$ first in your component
     * Note: Basic usage demonstrated. Your app may require more complicated account selection logic
     */
    let activeAccount = this.authService.instance.getActiveAccount();

    if (!activeAccount && this.authService.instance.getAllAccounts().length > 0) {
      let accounts = this.authService.instance.getAllAccounts();
      this.authService.instance.setActiveAccount(accounts[0]);
    }
  }


  loginRedirect() {
    this.authService.loginRedirect()
  }

  setLoginDisplay() {
    this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
  }

}
app.module.ts
export function MSALInstanceFactory(): IPublicClientApplication {
  return new PublicClientApplication({
    auth: {
      clientId: environment.msalConfig.auth.clientId,
      authority: environment.msalConfig.auth.authority,
      redirectUri: window.location.origin+"/login",
      postLogoutRedirectUri: window.location.origin+"/redirect-auth",
      navigateToLoginRequestUrl: true
    },
    cache: {
      cacheLocation: BrowserCacheLocation.LocalStorage,
    },
    system: {
      allowNativeBroker: false
    }
  })
}

  /**
   * Configuring the AAD login type and api related permissions
   * @returns AAD Guard configuration
   */
  export function MSALGuardConfigFactory(): MsalGuardConfiguration {
    return {
      interactionType: InteractionType.Redirect,
      authRequest: {
        scopes: [...environment.apiConfig.scopes]
      },
      loginFailedRoute: '/'
    }
  }

/**
 * Interceptor configuration for the API calls
 * @returns 
 */

export function MSALInterceptorConfig(): MsalInterceptorConfiguration {
  const protectedResourceMap = new Map<string, Array<string>>();
  protectedResourceMap.set(environment.apiConfig.uri, environment.apiConfig.scopes)
  return {
    interactionType: InteractionType.Redirect,
    protectedResourceMap
  }
}

@NgModule({
  declarations: [
    AppComponent,
    
  ],
  imports: [
    BrowserModule,
    MsalModule,
    
    AppRoutingModule,
    HttpClientModule,
    FormsModule,
    ReactiveFormsModule,
    NoopAnimationsModule ,
  
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: MsalInterceptor,
      multi: true
    },
    {
      provide: MSAL_INSTANCE,
      useFactory: MSALInstanceFactory,
    },
    {
      provide: MSAL_GUARD_CONFIG,
      useFactory: MSALGuardConfigFactory
    },
    {
      provide: MSAL_INTERCEPTOR_CONFIG,
      useFactory: MSALInterceptorConfig
    },
    MsalService,
    MsalGuard,
    MsalConfigService
  ],
  bootstrap: [AppComponent, MsalRedirectComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {
}
index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>SsoPoc</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
  <app-redirect></app-redirect>
</body>
</html>

package.json
{
  "name": "sso-poc",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^16.1.0-next.0",
    "@angular/common": "^16.1.0-next.0",
    "@angular/compiler": "^16.1.0-next.0",
    "@angular/core": "^16.1.0-next.0",
    "@angular/forms": "^16.1.0-next.0",
    "@angular/platform-browser": "^16.1.0-next.0",
    "@angular/platform-browser-dynamic": "^16.1.0-next.0",
    "@angular/router": "^16.1.0-next.0",
    "@azure/msal-angular": "^3.0.8",
    "@azure/msal-browser": "^3.5.0",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.13.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^16.1.0-next.0",
    "@angular/cli": "~16.1.0-next.0",
    "@angular/compiler-cli": "^16.1.0-next.0",
    "@types/jasmine": "~4.3.0",
    "jasmine-core": "~4.6.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.0.0",
    "typescript": "~5.0.2"
  }
}

Reproduction Steps

  1. Run ng serve in terminal

Expected Behavior

  • User should authenticate successfully with out any button click or once web page was loaded successfully .
  • User should navigate to next page once authentication was done

Identity Provider

Azure AD / MSA

Browsers Affected (Select all that apply)

Chrome, Firefox, Edge

Regression

No response

Source

Internal (Microsoft)

@Lakshman-Sundeep-Teki Lakshman-Sundeep-Teki added bug-unconfirmed A reported bug that needs to be investigated and confirmed question Customer is asking for a clarification, use case or information. labels Nov 13, 2023
@microsoft-github-policy-service microsoft-github-policy-service bot added the Needs: Attention 👋 Awaiting response from the MSAL.js team label Nov 13, 2023
@github-actions github-actions bot added msal-angular Related to @azure/msal-angular package msal-browser Related to msal-browser package public-client Issues regarding PublicClientApplications labels Nov 13, 2023
@Lakshman-Sundeep-Teki Lakshman-Sundeep-Teki changed the title BrowserAuthError: uninitialized_public_client_application: You must call and await the initialize function before attempting to call any other MSAL API. Angular 16: BrowserAuthError: uninitialized_public_client_application: You must call and await the initialize function before attempting to call any other MSAL API. Nov 13, 2023
@lalimasharda
Copy link
Contributor

Hey @Lakshman-Sundeep-Teki the error is pretty self-explanatory. The initialize method has to be the first step every time after creating a PCA instance.

@microsoft-github-policy-service microsoft-github-policy-service bot added Needs: Author Feedback Awaiting response from issue author and removed Needs: Attention 👋 Awaiting response from the MSAL.js team labels Nov 13, 2023
@Lakshman-Sundeep-Teki
Copy link
Author

which means do i need to initialize the msal instance before redirection ?

@microsoft-github-policy-service microsoft-github-policy-service bot added Needs: Attention 👋 Awaiting response from the MSAL.js team and removed Needs: Author Feedback Awaiting response from issue author labels Nov 13, 2023
@lalimasharda
Copy link
Contributor

Yes, you need to call the initialize() method before making any token requests or redirection requests. Please take a look at our docs for more guidance!

@microsoft-github-policy-service microsoft-github-policy-service bot added Needs: Author Feedback Awaiting response from issue author and removed Needs: Attention 👋 Awaiting response from the MSAL.js team labels Nov 13, 2023
@lalimasharda
Copy link
Contributor

Hey @Lakshman-Sundeep-Teki are you bootstraping the MsalRedirectComponent or subscribing to handleRedirectObservable in your app.component.ts? Please double check with our migration doc.

@Lakshman-Sundeep-Teki
Copy link
Author

Lakshman-Sundeep-Teki commented Nov 14, 2023

Hey @lalimasharda, Yes i'm bootstrapping the MsalRedirect component in app.module.ts. I am not using any handleRedirectObservable function. Let me try with initialize before redirection and will get back to you :) Here is my app.module.ts

export function MSALInstanceFactory(): IPublicClientApplication {
  return new PublicClientApplication({
    auth: {
      clientId: environment.msalConfig.auth.clientId,
      authority: environment.msalConfig.auth.authority,
      redirectUri: window.location.origin+"/login",
      postLogoutRedirectUri: window.location.origin+"/redirect-auth",
      navigateToLoginRequestUrl: true
    },
    cache: {
      cacheLocation: BrowserCacheLocation.LocalStorage,
    },
    system: {
      allowNativeBroker: false
    }
  })
}

  /**
   * Configuring the AAD login type and api related permissions
   * @returns AAD Guard configuration
   */
  export function MSALGuardConfigFactory(): MsalGuardConfiguration {
    return {
      interactionType: InteractionType.Redirect,
      authRequest: {
        scopes: [...environment.apiConfig.scopes]
      },
      loginFailedRoute: '/'
    }
  }

/**
 * Interceptor configuration for the API calls
 * @returns 
 */

export function MSALInterceptorConfig(): MsalInterceptorConfiguration {
  const protectedResourceMap = new Map<string, Array<string>>();
  protectedResourceMap.set(environment.apiConfig.uri, environment.apiConfig.scopes)
  return {
    interactionType: InteractionType.Redirect,
    protectedResourceMap
  }
}

@NgModule({
  declarations: [
    AppComponent,
    
  ],
  imports: [
    BrowserModule,
    MsalModule,
  
    AppRoutingModule,
    HttpClientModule,
    FormsModule,
    ReactiveFormsModule,
    NoopAnimationsModule ,
   
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: MsalInterceptor,
      multi: true
    },
    {
      provide: MSAL_INSTANCE,
      useFactory: MSALInstanceFactory,
    },
    {
      provide: MSAL_GUARD_CONFIG,
      useFactory: MSALGuardConfigFactory
    },
    {
      provide: MSAL_INTERCEPTOR_CONFIG,
      useFactory: MSALInterceptorConfig
    },
    MsalService,
    MsalGuard,
    MsalConfigService
  ],
  bootstrap: [AppComponent, MsalRedirectComponent], // Bootstrapping msal redirect
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }

@microsoft-github-policy-service microsoft-github-policy-service bot added Needs: Attention 👋 Awaiting response from the MSAL.js team and removed Needs: Author Feedback Awaiting response from issue author labels Nov 14, 2023
@Lakshman-Sundeep-Teki
Copy link
Author

@lalimasharda , I tried like this but i am facing the issue called
**BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API.**

Since i'm using the RedirectComponent istead of handleRedirectObservable i made the changes like this in app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  
})
export class AppComponent implements AfterViewInit, OnInit, OnDestroy {
 
  constructor(private router:Router,  @Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration,
  private msalbroadCastService: MsalBroadcastService,
  private authService: MsalService,) {}
  ngOnDestroy(): void {
  }

  ngAfterViewInit(): void {

  }

  async ngOnInit() {
    await this.authService.instance.initialize();
    await this.authService.instance.loginRedirect();
  }
}

Error i am getting:

**BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API.   For more visit: aka.ms/msaljs/browser-errors
BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API.   For more visit: aka.ms/msaljs/browser-errors**

Could you please let me know what am i missing here :)

@ludmillab
Copy link

I have the exact same problem.
I have looked at the sample application code: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/msal-angular-v3-samples/angular16-sample-app, but I cannot find anywhere in this sample code where the public client application is initialized.
If i initialize it in the app.module.ts, I get an error because the MSALInstanceFactory method is async, which it is not in your sample code.

@ludmillab
Copy link

I am calling authService.initialize() (not awaited) in the constructor of the app.component.ts. It returns an Observable object and I wonder if there should be a callback function in subscribe, to handle what should happen when the initialization is done.

@Lakshman-Sundeep-Teki
Copy link
Author

Are you getting BrowserAuthError: uninitialized_public_client_application: You must call and await the initialize function before attempting to call any other MSAL API. again when you are calling with out await ?

@ludmillab
Copy link

No. Here is my code (I modified it slightly).It is the ngOninit of app.component.ts:

ngOnInit(): void {
  this.authService.initialize()
    .subscribe(x => {
      //All the code that was previously in ngOnInit
      this.isIframe = window !== window.parent && !window.opener;

      this.msalBroadcastService.inProgress$
        .pipe(
          filter(
            (status: InteractionStatus) => status === InteractionStatus.None
          ),
          takeUntil(this._destroying$)
        )
        .subscribe(() => {
          this.setLoginDisplay();
        });
        ....
    });
}

@Lakshman-Sundeep-Teki
Copy link
Author

Is it working as expected ? Let me try this :)

@Lakshman-Sundeep-Teki
Copy link
Author

@ludmillab , If i am doing like this i am getting error like below
BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API.

I am using msal redirection component in my application

after modification my code in app.component.ts at ngOnInit

ngOnInit() {
    this.authService.initialize().subscribe((x) => {
          this.authService.loginRedirect();
   })
  }

What am i missing here ?

@ludmillab
Copy link

ludmillab commented Nov 14, 2023

I'm not sure.
When I look at the code in the sample code, I can see that you need to add some stuff to the ngOnInit.
Look at the code here: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/samples/msal-angular-v3-samples/angular16-sample-app/src/app/app.component.ts

ngOnInit(): void {
    this.isIframe = window !== window.parent && !window.opener; // Remove this line to use Angular Universal
    this.setLoginDisplay();

    this.authService.instance.enableAccountStorageEvents(); // Optional - This will enable ACCOUNT_ADDED and ACCOUNT_REMOVED events emitted when a user logs in or out of another tab or window
    this.msalBroadcastService.msalSubject$
      .pipe(
        filter((msg: EventMessage) => msg.eventType === EventType.ACCOUNT_ADDED || msg.eventType === EventType.ACCOUNT_REMOVED),
      )
      .subscribe((result: EventMessage) => {
        if (this.authService.instance.getAllAccounts().length === 0) {
          window.location.pathname = "/";
        } else {
          this.setLoginDisplay();
        }
      });
    
    this.msalBroadcastService.inProgress$
      .pipe(
        filter((status: InteractionStatus) => status === InteractionStatus.None),
        takeUntil(this._destroying$)
      )
      .subscribe(() => {
        this.setLoginDisplay();
        this.checkAndSetActiveAccount();
      })
  }

@ludmillab
Copy link

ludmillab commented Nov 14, 2023

That would be:

ngOnInit(): void {
this.authService.initialize()
  .subscribe(x => {
    this.isIframe = window !== window.parent && !window.opener; // Remove this line to use Angular Universal
    this.setLoginDisplay();

    this.authService.instance.enableAccountStorageEvents(); // Optional - This will enable ACCOUNT_ADDED and ACCOUNT_REMOVED events emitted when a user logs in or out of another tab or window
    this.msalBroadcastService.msalSubject$
      .pipe(
        filter((msg: EventMessage) => msg.eventType === EventType.ACCOUNT_ADDED || msg.eventType === EventType.ACCOUNT_REMOVED),
      )
      .subscribe((result: EventMessage) => {
        if (this.authService.instance.getAllAccounts().length === 0) {
          window.location.pathname = "/";
        } else {
          this.setLoginDisplay();
        }
      });
    
    this.msalBroadcastService.inProgress$
      .pipe(
        filter((status: InteractionStatus) => status === InteractionStatus.None),
        takeUntil(this._destroying$)
      )
      .subscribe(() => {
        this.setLoginDisplay();
        this.checkAndSetActiveAccount();
      });
});
  }

@muhamedkarajic
Copy link

Any progress on this cuase we need really to update to Angular 17 and it seems msal is causing the issue that its not compatible with esmodule?

@Lakshman-Sundeep-Teki
Copy link
Author

@muhamedkarajic, Not sure this is a correct way to implement like this. But i made a work around like the below :). Hope this help you

this.msalbroadCastService.inProgress$.pipe(
    filter((status: InteractionStatus) => status === InteractionStatus.None),
    takeUntil(this._destroy)
  ).subscribe(() => {
     if(!(this.userExists())) { // Checking if user already logged in or not during page load
      this.timer_task = setTimeout(() => {
        this.authService.loginRedirect(); // redirecting after 5 seconds
      }, 5000)
      
     } else {
      this.router.navigate(["landing-page"]) // navigate to dashboard page or landing page if user exists
     }
  })

@tnorling
Copy link
Collaborator

@Lakshman-Sundeep-Teki I would not recommend setting the 5 second timeout. Invoking loginRedirect after the InteractionStatus becomes None should be sufficient on its own and is our recommended best practice.

@microsoft-github-policy-service microsoft-github-policy-service bot added Needs: Author Feedback Awaiting response from issue author and removed Needs: Attention 👋 Awaiting response from the MSAL.js team labels Dec 11, 2023
@muhamedkarajic
Copy link

@muhamedkarajic, Not sure this is a correct way to implement like this. But i made a work around like the below :). Hope this help you

Don't know we use Angular 17 and what I have for an issue is that its not building properly. I think this is required to be done to make the lib be on Angular 17. Sorry for misunderstanding.

@microsoft-github-policy-service microsoft-github-policy-service bot added the no-issue-activity Issue author has not responded in 5 days label Dec 18, 2023
@Trapulo
Copy link

Trapulo commented Jun 18, 2024

I solved with

msAuthService.initialize().subscribe(result => {

})    

in my service's constructor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug-unconfirmed A reported bug that needs to be investigated and confirmed msal-angular Related to @azure/msal-angular package msal-browser Related to msal-browser package Needs: Author Feedback Awaiting response from issue author no-issue-activity Issue author has not responded in 5 days public-client Issues regarding PublicClientApplications question Customer is asking for a clarification, use case or information.
Projects
None yet
Development

No branches or pull requests

6 participants