AzureAD / microsoft-authentication-library-for-js

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

After clicking Login button, I'm getting redirected to the same page. #3021

Closed JudeAlquiza closed 3 years ago

JudeAlquiza commented 3 years ago

Hi, I'm having this problem where in when I click on the login button as shown below, I'm redirected to the same page.

image

here's the log from the dev tools

image

I tried to run this example https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/msal-angular-v2-samples/angular11-b2c-sample locally and it works fine with the Azure AD B2C configurations updated with the one I setup on my personal Azure AD B2C account.

But I'm having problems when I'm trying to apply that to my own Angular v11 project.

Here's my package.json

"dependencies": {
    "@angular/animations": "~11.2.0",
    "@angular/common": "~11.2.0",
    "@angular/compiler": "~11.2.0",
    "@angular/core": "~11.2.0",
    "@angular/forms": "~11.2.0",
    "@angular/platform-browser": "~11.2.0",
    "@angular/platform-browser-dynamic": "~11.2.0",
    "@angular/router": "~11.2.0",
    "@azure/msal-angular": "^2.0.0-alpha.5",
    "@azure/msal-browser": "^2.11.1",
    "bootstrap": "^5.0.0-beta1",
    "rxjs": "~6.6.0",
    "tslib": "^2.0.0",
    "zone.js": "~0.11.3"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.1101.4",
    "@angular/cli": "~11.1.4",
    "@angular/compiler-cli": "~11.2.0",
    "@types/jasmine": "~3.6.0",
    "@types/node": "^12.11.1",
    "codelyzer": "^6.0.0",
    "jasmine-core": "~3.6.0",
    "jasmine-spec-reporter": "~5.0.0",
    "karma": "~5.2.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.0.3",
    "karma-jasmine": "~4.0.0",
    "karma-jasmine-html-reporter": "^1.5.0",
    "protractor": "~7.0.0",
    "ts-node": "~8.3.0",
    "tslint": "~6.1.0",
    "typescript": "~4.1.5"
  }

My app.module.ts and b2c-config.ts is the same as in the angular11-b2c-sample with config updated to my own config.

app.module.ts.

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MsalBroadcastService, MsalGuard, MsalGuardConfiguration, MsalInterceptor, MsalInterceptorConfiguration, MsalModule, MsalRedirectComponent, MsalService, MSAL_GUARD_CONFIG, MSAL_INSTANCE, MSAL_INTERCEPTOR_CONFIG } from '@azure/msal-angular';
import { BrowserCacheLocation, InteractionType, IPublicClientApplication, LogLevel, PublicClientApplication } from '@azure/msal-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';
import { HomeComponent } from './home/home.component';
import { b2cPolicies, apiConfig } from './b2c-config';
import { environment } from 'src/environments/environment';

const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1;

export function loggerCallback(logLevel: LogLevel, message: string) {
  console.log(message);
}

export function MSALInstanceFactory(): IPublicClientApplication {
  return new PublicClientApplication({
    auth: {
      clientId: '<my client id>',
      authority: b2cPolicies.authorities.signUpSignIn.authority,
      redirectUri: environment.redirectUri,
      postLogoutRedirectUri: environment.postLogoutRedirectUri,
      knownAuthorities: [b2cPolicies.authorityDomain]
    },
    cache: {
      cacheLocation: BrowserCacheLocation.LocalStorage,
      storeAuthStateInCookie: isIE, // set to true for IE 11
    },
    system: {
      loggerOptions: {
        loggerCallback,
        logLevel: LogLevel.Info,
        piiLoggingEnabled: false
      }
    }
  });
}

export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
  const protectedResourceMap = new Map<string, Array<string>>();
  // protectedResourceMap.set(apiConfig.uri, apiConfig.scopes);

  return {
    interactionType: InteractionType.Redirect,
    protectedResourceMap,
  };
}

export function MSALGuardConfigFactory(): MsalGuardConfiguration {
  return {
    interactionType: InteractionType.Redirect,
    authRequest: {
      scopes: [] // [...apiConfig.scopes],
    },
  };
}

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    CoreModule,
    MsalModule,
    AppRoutingModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: MsalInterceptor,
      multi: true
    },
    {
      provide: MSAL_INSTANCE,
      useFactory: MSALInstanceFactory
    },
    {
      provide: MSAL_GUARD_CONFIG,
      useFactory: MSALGuardConfigFactory
    },
    {
      provide: MSAL_INTERCEPTOR_CONFIG,
      useFactory: MSALInterceptorConfigFactory
    },
    MsalService,
    MsalGuard,
    MsalBroadcastService
  ],
  bootstrap: [AppComponent, MsalRedirectComponent]
})
export class AppModule { }

routing is a bit different, here's my app.component.html

<!-- Wrapper-->
<div id="wrapper">

  <!-- Main page wrapper -->
  <div id="page-wrapper" class="gray-bg">

      <!-- Top navigation -->
      <app-main-nav></app-main-nav>

      <!-- Main view/routes wrapper-->
      <router-outlet></router-outlet>

      <!-- Top navigation -->
      <app-main-footer></app-main-footer>

  </div>
  <!-- End page wrapper-->

</div>
<!-- End wrapper-->

app.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{

  ngOnInit(): void {

  }
}

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MsalGuard } from '@azure/msal-angular';
import { HomeComponent } from './home/home.component';

const routes: Routes = [
  {
    path: 'chat', loadChildren: () => import('./chat/chat.module').then(m => m.ChatModule),
    canActivate: [
      MsalGuard,
    ]
  },
  { path: 'home', component: HomeComponent },
  { path: '', redirectTo: 'home', pathMatch: 'full' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

my default page is in the HomeComponent

home.component.html

<div class="container">
  <div class="row">
    <div class="col-sm">
    </div>
    <div class="col-sm">
      <div class="form-signin">
        <form>
          <button class="w-100 btn btn-lg btn-primary" type="submit" style="margin-top: 130%;"
            (click)="login()">Login</button>
          <div style="margin-top: 10px;">Don't have an account yet ? Register <a href="#">here</a>.</div>
        </form>
      </div>
    </div>
  </div>
</div>

the code for my home.component.ts is the same as the app.component.ts in the angular11-b2c-sample, I just took out things that I don't need.

hope.component.ts

import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { MsalBroadcastService, MsalGuardConfiguration, MsalService, MSAL_GUARD_CONFIG } from '@azure/msal-angular';
import { AuthenticationResult, AuthError, EventMessage, EventType, InteractionStatus, InteractionType, PopupRequest, RedirectRequest } from '@azure/msal-browser';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { b2cPolicies } from '../b2c-config';

interface IdTokenClaims extends AuthenticationResult {
  idTokenClaims: {
    acr?: string
  }
}

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit, OnDestroy {
  private readonly _destroying$ = new Subject<void>();

  constructor(
    @Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration,
    private authService: MsalService,
    private msalBroadcastService: MsalBroadcastService
  ) {}

  ngOnInit(): void {
    this.msalBroadcastService.msalSubject$
      .pipe(
        filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS),
      )
      .subscribe({
        next: (result: EventMessage) => {
          console.log(result);
          if (result?.payload?.account) {
            this.authService.instance.setActiveAccount(result.payload.account);
          }
        },
        error: (error) => console.log(error)
      });

    this.msalBroadcastService.msalSubject$
      .pipe(
        filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS || msg.eventType === EventType.ACQUIRE_TOKEN_SUCCESS),
        takeUntil(this._destroying$)
      )
      .subscribe((result: EventMessage) => {

        let payload: IdTokenClaims = <AuthenticationResult>result.payload;

        // We need to reject id tokens that were not issued with the default sign-in policy.
        // "acr" claim in the token tells us what policy is used (NOTE: for new policies (v2.0), use "tfp" instead of "acr")
        // To learn more about b2c tokens, visit https://docs.microsoft.com/en-us/azure/active-directory-b2c/tokens-overview

        if (payload.idTokenClaims?.acr === b2cPolicies.names.forgotPassword) {
          window.alert('Password has been reset successfully. \nPlease sign-in with your new password.');
          return this.authService.logout();
        } else if (payload.idTokenClaims['acr'] === b2cPolicies.names.editProfile) {
          window.alert('Profile has been updated successfully. \nPlease sign-in again.');
          return this.authService.logout();
        }

        return result;
      });

      this.msalBroadcastService.msalSubject$
      .pipe(
        filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_FAILURE || msg.eventType === EventType.ACQUIRE_TOKEN_FAILURE),
        takeUntil(this._destroying$)
      )
      .subscribe((result: EventMessage) => {
        if (result.error instanceof AuthError) {
          // Check for forgot password error
          // Learn more about AAD error codes at https://docs.microsoft.com/azure/active-directory/develop/reference-aadsts-error-codes
          if (result.error.message.includes('AADB2C90118')) {

            // login request with reset authority
            let resetPasswordFlowRequest = {
              scopes: ["openid"],
              authority: b2cPolicies.authorities.forgotPassword.authority,
            };

            this.login(resetPasswordFlowRequest);
          }
        }
      });
  }

  login(userFlowRequest?: RedirectRequest | PopupRequest) {
    if (this.msalGuardConfig.interactionType === InteractionType.Popup) {
      if (this.msalGuardConfig.authRequest) {
        this.authService.loginPopup({...this.msalGuardConfig.authRequest, ...userFlowRequest} as PopupRequest)
          .subscribe((response: AuthenticationResult) => {
            this.authService.instance.setActiveAccount(response.account);
          });
      } else {
        this.authService.loginPopup(userFlowRequest)
          .subscribe((response: AuthenticationResult) => {
            this.authService.instance.setActiveAccount(response.account);
          });
      }
    } else {
      if (this.msalGuardConfig.authRequest){
        this.authService.loginRedirect({...this.msalGuardConfig.authRequest, ...userFlowRequest} as RedirectRequest);
      } else {
        this.authService.loginRedirect(userFlowRequest);
      }
    }
  }

  logout() {
    this.authService.logout();
  }

  ngOnDestroy(): void {
    this._destroying$.next(undefined);
    this._destroying$.complete();
  }
}

checking in vs code I'm getting this error in the debugger console.

image

really need your help I'm kind of stuck in this for the past couple of days now, thanks in advance. :)

PS: one thing I noticed is that when I navigate directly to a route with a MsalGuard routeguard, I'm able to login but after login I'm getting this in the chrome dev tools console.

image

another thing I noticed is when I force it to use login.popup after clicking login button, I'm only getting a blank popup.

JudeAlquiza commented 3 years ago

I just found the problem, in the home.component.html

<div class="container">
  <div class="row">
    <div class="col-sm">
    </div>
    <div class="col-sm">
      <div class="form-signin">
        <form>
          <button class="w-100 btn btn-lg btn-primary" type="submit" style="margin-top: 130%;"
            (click)="login()">Login</button>
          <div style="margin-top: 10px;">Don't have an account yet ? Register <a href="#">here</a>.</div>
        </form>
      </div>
    </div>
  </div>
</div>

my button is inside of the form tag and type is set to submit, I just removed type="submit" and put the button outside of the form tag. closing this issue now. thanks.