'Redirected to login page for multiple times before successfully log in to application (Microsoft Azure AD with Angular 12 and ASP.NET Core)
I'm using angular 12 with ASP.NET Core and successfully integrated my application with Azure AD clouds and got the token from it. But there are some issues (Bugs) that I'm being redirected to the login page multiple times while logging in to the application before successfully logging in. What I want to do is log in normally from the login page to the dashboard application without the issues.
I already configured the application by following this tutorial from Microsoft Angular v2: https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-angular-auth-code
These what I've done :
#1 Msal packaged installed : package,json
#2 app.module.ts :
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
// env
// import { environment } from './../environments/environment.prod';
import { envtest } from './../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { FormsModule } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { NgxEchartsModule } from 'ngx-echarts';
import { MatDialogModule } from '@angular/material/dialog';
// MSAL
import {
IPublicClientApplication,
PublicClientApplication,
InteractionType,
BrowserCacheLocation,
LogLevel,
} from '@azure/msal-browser';
import {
MsalGuard,
MsalInterceptor,
MsalBroadcastService,
MsalInterceptorConfiguration,
MsalModule,
MsalService,
MSAL_GUARD_CONFIG,
MSAL_INSTANCE,
MSAL_INTERCEPTOR_CONFIG,
MsalGuardConfiguration,
MsalRedirectComponent,
} from '@azure/msal-angular';
// primeng
import { ProgressBarModule } from 'primeng/progressbar';
import { DropdownModule } from 'primeng/dropdown';
import { CheckboxModule } from 'primeng/checkbox';
import { CalendarModule } from 'primeng/calendar';
import { TableModule } from 'primeng/table';
import { PaginatorModule } from 'primeng/paginator';
import { InputTextModule } from 'primeng/inputtext';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { InputNumberModule } from 'primeng/inputnumber';
import { PasswordModule } from 'primeng/password';
import { TooltipModule } from 'primeng/tooltip';
import { ToastModule } from 'primeng/toast';
import { DialogModule } from 'primeng/dialog';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { DynamicDialogModule } from 'primeng/dynamicdialog';
import { MessagesModule } from 'primeng/messages';
import { MessageModule } from 'primeng/message';
import { TabViewModule } from 'primeng/tabview';
import { MenuModule } from 'primeng/menu';
import { TieredMenuModule } from 'primeng/tieredmenu';
import { LoginComponent } from './pages/login/login.component';
import { NotfoundComponent } from './pages/notfound/notfound.component';
import { DashboardComponent } from './pages/dashboard/dashboard.component';
import { MainlayoutComponent } from './components/mainlayout/mainlayout.component';
import { StudentByCampusComponent } from './components/student-by-campus/student-by-campus.component';
import { StudentEnrichComponent } from './pages/student-enrich/student-enrich.component';
import { StudentDetailComponent } from './pages/student-enrich/detail/student-detail/student-detail.component';
import { StudentExportComponent } from './pages/student-enrich/export/student-export/student-export.component';
import { StudentSearchComponent } from './pages/student-enrich/student-search/student-search.component';
import { SimpleDemoComponent } from './components/echarts/simple-demo/simple-demo.component';
import { EcBarChartComponent } from './components/echarts/ec-bar-chart/ec-bar-chart.component';
import { TrackByCampusComponent } from './components/track-by-campus/track-by-campus.component';
import { StudentByFacultyComponent } from './components/student-by-faculty/student-by-faculty.component';
import { TrackByFacultyComponent } from './components/track-by-faculty/track-by-faculty.component';
import { DetailTrackByCampusComponent } from './components/detail-track-by-campus/detail-track-by-campus.component';
import { DetailTrackByFacultyComponent } from './components/detail-track-by-faculty/detail-track-by-faculty.component';
import { UserComponent } from './pages/user/user/user.component';
import { PlacementEnrichmentComponent } from './pages/placement-enrichment/placement-enrichment.component';
import { AvailablePositionComponent } from './pages/available-position/available-position.component';
import { EnrichmentPartnerComponent } from './pages/enrichment-partner/enrichment-partner.component';
import { GradingCategoryComponent } from './pages/grading-category/grading-category.component';
import { UserFormComponent } from './pages/user/user-form/user-form.component';
import { ResetPasswordComponent } from './pages/user/reset-password/reset-password.component';
import { ApiService } from './shared/http-service/api.service';
import { AppSettings } from './shared/appSettings';
import { EmptyStateComponent } from './components/skeletton/empty-state/empty-state.component';
import { StudentByProgramComponent } from './components/student-by-program/student-by-program.component';
import { TrackByProgramComponent } from './components/track-by-program/track-by-program.component';
import { DetailTrackByProgramComponent } from './components/detail-track-by-program/detail-track-by-program.component';
import { HttpInterceptorService } from './shared/http-service/httpInterceptor-service';
import { SessionExpiredComponent } from './pages/session-expired/session-expired.component';
import { TwosideStudentEnrichComponent } from './pages/student-enrich/twoside-student-enrich/twoside-student-enrich.component';
import { CheckroleDirective } from './shared/role/checkrole.directive';
// primeng
const isIE =
window.navigator.userAgent.indexOf('MSIE ') > -1 ||
window.navigator.userAgent.indexOf('Trident/') > -1;
@NgModule({
declarations: [
AppComponent,
MainlayoutComponent,
LoginComponent,
NotfoundComponent,
DashboardComponent,
StudentByCampusComponent,
StudentEnrichComponent,
StudentDetailComponent,
StudentExportComponent,
StudentSearchComponent,
SimpleDemoComponent,
EcBarChartComponent,
TrackByCampusComponent,
StudentByFacultyComponent,
TrackByFacultyComponent,
DetailTrackByCampusComponent,
DetailTrackByFacultyComponent,
UserComponent,
PlacementEnrichmentComponent,
AvailablePositionComponent,
EnrichmentPartnerComponent,
GradingCategoryComponent,
UserFormComponent,
ResetPasswordComponent,
EmptyStateComponent,
StudentByProgramComponent,
TrackByProgramComponent,
DetailTrackByProgramComponent,
SessionExpiredComponent,
TwosideStudentEnrichComponent,
CheckroleDirective,
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FontAwesomeModule,
NgSelectModule,
BrowserAnimationsModule,
FormsModule,
ProgressBarModule,
DropdownModule,
CheckboxModule,
CalendarModule,
TableModule,
PaginatorModule,
InputTextModule,
InputTextareaModule,
InputNumberModule,
PasswordModule,
TooltipModule,
ToastModule,
DialogModule,
ConfirmDialogModule,
DynamicDialogModule,
MessagesModule,
MessageModule,
TabViewModule,
MenuModule,
TieredMenuModule,
MatDialogModule,
NgxEchartsModule.forRoot({
echarts: () => import('echarts'), // or import('./path-to-my-custom-echarts')
}),
MsalModule,
],
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,
},
{
provide: HTTP_INTERCEPTORS,
useClass: HttpInterceptorService,
multi: true,
},
ApiService,
AppSettings,
MsalService,
MsalGuard,
MsalBroadcastService,
],
bootstrap: [AppComponent, MsalRedirectComponent],
})
export class AppModule {}
export function loggerCallback(logLevel: LogLevel, message: string) {
console.log(message);
}
export function MSALInstanceFactory(): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: envtest.clientId,
authority: envtest.authority, // tenantID
redirectUri: envtest.redirectUri,
postLogoutRedirectUri: envtest.postLogoutRedirectUri,
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE, // set to true for IE 11. Remove this line to use Angular Universal
},
system: {
loggerOptions: {
loggerCallback,
logLevel: LogLevel.Info,
piiLoggingEnabled: false,
},
},
});
}
// export function init_app(appSettings: AppSettings) {
// return () => {
// appSettings.load();
// };
// }
export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
const protectedResourceMap = new Map<string, Array<string>>();
protectedResourceMap.set('https://graph.microsoft.com/v1.0/me', [
'user.read',
]); // Prod environment. Uncomment to use.
// protectedResourceMap.set('https://graph.microsoft-ppe.com/v1.0/me', [
// 'user.read',
// ]);
return {
interactionType: InteractionType.Redirect,
protectedResourceMap,
};
}
export function MSALGuardConfigFactory(): MsalGuardConfiguration {
return {
interactionType: InteractionType.Redirect,
authRequest: {
scopes: ['user.read'],
},
};
}
#3 login.component.ts :
import { Component, OnInit, Inject } from '@angular/core';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { Router } from '@angular/router';
// msal import
import {
MsalService,
MsalBroadcastService,
MSAL_GUARD_CONFIG,
MsalGuardConfiguration,
} from '@azure/msal-angular';
import {
AuthenticationResult,
InteractionStatus,
InteractionType,
PopupRequest,
RedirectRequest,
} from '@azure/msal-browser';
import { EventMessage, EventType } from '@azure/msal-browser';
// import { Injectable } from '@angular/core';
import { User, UserResponse } from 'src/app/types/UserResponse';
import { ApiService } from 'src/app/shared/http-service/api.service';
import { TokenService } from 'src/app/shared/TokenService';
import { AppSettings } from 'src/app/shared/appSettings';
// rxjs
import { Subject, throwError } from 'rxjs';
import { catchError, filter, takeUntil, window } from 'rxjs/operators';
import { result } from 'lodash';
import { UserService } from 'src/app/core/user/user.service';
import Swal from 'sweetalert2';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
})
// @Injectable({
// providedIn: 'root',
// })
export class LoginComponent implements OnInit {
loginDisplay = false;
private readonly _destroying$ = new Subject<void>();
constructor(
@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration,
private _router: Router,
private authService: MsalService,
private apiService: ApiService,
private tokenService: TokenService,
private msalBroadcastService: MsalBroadcastService,
private _userService: UserService
) { }
public username: string = '';
public password: string = '';
message: string = '';
messages: any[] = [];
login_invalid: boolean = false;
loading: boolean = false;
loadingMsal: boolean = false;
faSpinner = faSpinner;
user: UserResponse = new UserResponse();
// filtered
filteredCampus: any = [];
filteredFact: any = [];
filteredProg: any = [];
ngOnInit(): void {
this._userService.refreshData();
const userLocal = localStorage.getItem('AUTH') || '';
const user: UserResponse =
userLocal == '' ? undefined : JSON.parse(userLocal);
if (user) {
this.user = user;
this._router.navigate(['/student_enrichment']);
}
this.msalBroadcastService.msalSubject$
.pipe(
filter(
(msg: EventMessage) =>
msg.eventType === EventType.ACQUIRE_TOKEN_SUCCESS
),
takeUntil(this._destroying$)
)
.subscribe((result: EventMessage) => {
const res: any = {
...(result as any).payload.account,
success: true,
};
this.username = res.username;
const userData = JSON.stringify(res);
localStorage.setItem(
'AUTH',
JSON.stringify({
...res,
success: true,
fromAzure: true,
})
);
this.getUserInformation();
this.fetchUserRole();
});
this.msalBroadcastService.inProgress$
.pipe(
filter(
(status: InteractionStatus) => status === InteractionStatus.None
),
takeUntil(this._destroying$)
)
.subscribe((val) => {
this.setLoginDisplay();
});
}
setLoginDisplay() {
this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
}
// isLoggedIn() {
// return this.authService.instance.getActiveAccount() != null;
// }
loginMsal() {
this.loadingMsal = true;
this.authService.loginRedirect({
...this.msalGuardConfig.authRequest,
} as RedirectRequest);
this.getUserInformation();
}
login() {
this.loading = true;
this.message = '';
this.login_invalid = false;
const form = {
Email: this.username,
Password: this.password,
};
this.apiService.post('api/auth/login', form)
.subscribe(
(data: any) => {
if (data.status) {
localStorage.setItem('AUTH', JSON.stringify(data));
// this._router.navigate(['/student_enrichment']);
this.getUserInformation();
this.fetchUserRole();
} else {
this.loading = false;
Swal.fire({
title: 'Invalid User Data',
text: data.message ?? 'Invalid User Data',
icon: 'error'
})
// return console.log('Invalid User Data');
}
},
(error: any) => {
if (error.error) {
if (error.error.message) {
Swal.fire({
title: 'Invalid',
text: error.error.message ?? 'Invalid User Data',
icon: 'error'
})
}
}
this.loading = false;
}
)
;
this._userService.refreshData();
}
getUserInformation() {
this.apiService.getToken(`api/auth/token`).subscribe(
(data: any) => {
if (data.data.token) {
localStorage.setItem('AUTH_TOKEN', data.data.token);
this.tokenService.clearMessages();
this.tokenService.sendMessage(data.data.token);
this.getDropdownData();
}
},
(error: any) => {
this.loading = false;
}
);
}
getDropdownData() {
const promise1 = this.apiService.get(`api/dimcampus`).toPromise();
promise1
.then((data: any) => {
const dropDowns = [
{
campusId: '',
campusDesc: 'All Campus',
},
...data.data,
];
if (data.data) {
const result = JSON.stringify(dropDowns);
localStorage.setItem('DDCAMPUS', result);
}
})
.catch((e) => {
console.log(e.message);
});
const promise2 = this.apiService.get(`api/dimacademicgroup`).toPromise();
promise2.then((data: any) => {
const dropDowns = [
{
acadGroupId: '',
acadGroupDesc: 'All Faculty',
},
...data.data,
];
// const filtered = dropDowns.filter(
// (item: any) => item.campusId === this.user.data!.email!.campusId
// );
if (data) {
const result = JSON.stringify(dropDowns);
localStorage.setItem('DDFACULTY', result);
}
});
const promise3 = this.apiService.get(`api/dimterm`).toPromise();
promise3.then((data: any) => {
if (data) {
// data.data.unshift({ strm: ' ', strmDesc: '' });
const result = JSON.stringify(data.data);
localStorage.setItem('STRM', result);
}
});
const promise4 = this.apiService.get(`api/dimacademicprogram`).toPromise();
promise4.then((data: any) => {
const dropDowns = [
{
acadProgId: '',
acadProgDesc: 'All Program',
},
...data.data,
];
const result = JSON.stringify(dropDowns);
localStorage.setItem('PROGRAM', result);
});
const promise5 = this.apiService.get(`api/dimtrack`).toPromise();
promise5.then((data: any) => {
if (data) {
const result = JSON.stringify(data.data);
localStorage.setItem('TRACK', result);
}
});
Promise.all([promise1, promise2, promise3, promise4, promise5])
.then((data: any) => {
this.loading = false;
this._router.navigate(['/student_enrichment']);
})
.catch((error: any) => {
this.loading = false;
});
}
fetchUserRole() {
const promise = this.apiService
.post(`api/auth/role?Email=${this.username}`, null)
.toPromise();
promise.then((data: any) => {
if (data) {
this.user = data;
const result = JSON.stringify(data);
localStorage.setItem('USERDATA', result);
}
});
}
ngOnDestroy() {
this._destroying$.next(undefined);
this._destroying$.complete();
}
}
#4 app-routing.module.ts :
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MsalGuard } from '@azure/msal-angular';
import { LoginComponent } from './pages/login/login.component';
import { NotfoundComponent } from './pages/notfound/notfound.component';
import { DashboardComponent } from './pages/dashboard/dashboard.component';
import { MainlayoutComponent } from './components/mainlayout/mainlayout.component';
import { StudentEnrichComponent } from './pages/student-enrich/student-enrich.component';
import { PlacementEnrichmentComponent } from './pages/placement-enrichment/placement-enrichment.component';
import { AvailablePositionComponent } from './pages/available-position/available-position.component';
import { EnrichmentPartnerComponent } from './pages/enrichment-partner/enrichment-partner.component';
import { GradingCategoryComponent } from './pages/grading-category/grading-category.component';
import { UserComponent } from './pages/user/user/user.component';
import { UserFormComponent } from './pages/user/user-form/user-form.component';
import { ResetPasswordComponent } from './pages/user/reset-password/reset-password.component';
import { SessionExpiredComponent } from './pages/session-expired/session-expired.component';
import { UserResolver } from './pages/user/user.resolver';
const routes: Routes = [
{ path: '', component: LoginComponent },
{
path: '',
component: MainlayoutComponent,
children: [
{ path: '', component: StudentEnrichComponent, pathMatch: 'full' },
{
path: 'student_enrichment',
component: StudentEnrichComponent,
// canActivate: [MsalGuard],
pathMatch: 'full',
},
{ path: 'placement_enrichment', component: PlacementEnrichmentComponent },
{ path: 'available_position', component: AvailablePositionComponent },
{ path: 'enrichment_partner', component: EnrichmentPartnerComponent },
{ path: 'grading_category', component: GradingCategoryComponent },
{
path: 'users',
component: UserComponent,
resolve: {
users: UserResolver,
},
},
{ path: 'user_form', component: UserFormComponent },
{ path: 'user_form/:id', component: UserFormComponent },
],
},
{ path: 'expired', component: SessionExpiredComponent },
// { path: 'login', component: LoginComponent },
{ path: 'reset', component: ResetPasswordComponent },
{ path: '**', component: NotfoundComponent },
];
const isIframe = window !== window.parent && !window.opener;
@NgModule({
imports: [
RouterModule.forRoot(routes, {
// useHash: true,
initialNavigation: !isIframe ? 'enabled' : 'disabled',
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
#5 I put the tenant and client id in separated files within the environtment.ts with the same code structures from Azure AD:
export const envtest = {
production: false,
apiRoute: 'https://api-my-domain/',
authorization: 'my-auth',
/// Azure AD config
clientId: 'my-client-id-from-azure-ad', // clientID
authority:
'https://login.microsoftonline.com/my-tenant-id-from-azure-ad', // tenantID
redirectUri: 'https://localhost:4600/app-end-point',
postLogoutRedirectUri: 'https://localhost:4600',
};
I'm new here with Angular and also with Azure ad. Please help to solve my issues Thank you.
Solution 1:[1]
• I followed the same link to build an angular application with Azure AD authentication that you followed but I didn’t encounter any such issue while logging in to the application with Azure AD credentials. Also, I ensured that the prerequisites for creating the Angular application are met by following the application registration link in the documentation below.
Angular application creation link: - https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-angular-auth-code
Angular application prerequisites link: - https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration
Please find below the screenshots of the output that is displayed after running the angular application build and running the application in the browser: -
Also, find below snapshots of the Visual Studio code executed while building the Angular application: -
This application was executed successfully in my environment without any issues while logging in. Thus, please check the redirect URLs configured in your application in Azure AD and the ‘app.module.ts’ file in Angular app as well as the application ID URI that is configured in your application files and in Azure AD respectively.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | KartikBhiwapurkar-MT |




