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

Feature/msal #245

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,632 changes: 2,611 additions & 2,021 deletions Singer.API/ClientApp/package-lock.json

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions Singer.API/ClientApp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Singer",
"version": "0.1.0-PullRequest0237.1582",
"version": "0.1.0-issue-msal.1592",
"scripts": {
"ng": "ng",
"start": "ng serve",
Expand All @@ -27,6 +27,9 @@
"@angular/platform-server": "14.3.0",
"@angular/router": "14.3.0",
"@auth0/angular-jwt": "^5.0.2",
"@azure/msal-angular": "^2.5.7",
"@azure/msal-browser": "^2.38.3",
"@codewithdan/observable-store": "^2.2.15",
"@danielmoncada/angular-datetime-picker": "^14.2.0",
"angular-calendar": "^0.28.2",
"date-fns": "^2.9.0",
Expand All @@ -52,9 +55,9 @@
"karma-coverage-istanbul-reporter": "~1.4.2",
"karma-jasmine": "~1.1.1",
"karma-jasmine-html-reporter": "^0.2.2",
"typescript": "~4.6.4",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0"
"tslint": "~6.1.0",
"typescript": "~4.6.4"
}
}
18 changes: 13 additions & 5 deletions Singer.API/ClientApp/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import { Routes, RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { MainComponent } from './main.component';
import { BrowserUtils } from '@azure/msal-browser';
import { MsalRedirectComponent } from '@azure/msal-angular';

const routes: Routes = [
{ path: 'login', loadChildren: () => import('./modules/login/login.module').then(m => m.LoginModule) },
{ path: 'login', loadChildren: () => import('./modules/login/login.module').then((m) => m.LoginModule) },
{
// Needed for handling redirect after login
path: 'auth',
loadChildren: () => import('./modules/login/login.module').then(m => m.LoginModule),
component: MsalRedirectComponent,
},
{
path: 'dashboard',
loadChildren: () => import('./modules/dashboard/dashboard.module').then(m => m.DashboardModule),
loadChildren: () => import('./modules/dashboard/dashboard.module').then((m) => m.DashboardModule),
component: MainComponent,
},
{
path: 'voogden',
loadChildren: () => import('./modules/legalguardians/legalguardians.module').then(m => m.LegalguardiansModule),
loadChildren: () => import('./modules/legalguardians/legalguardians.module').then((m) => m.LegalguardiansModule),
component: MainComponent,
},
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
];

@NgModule({
imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })],
imports: [
RouterModule.forRoot(routes, {
relativeLinkResolution: 'legacy', // Don't perform initial navigation in iframes or popups
initialNavigation: !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup() ? 'enabledNonBlocking' : 'disabled', // Set to enabledBlocking to use Angular Universal
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
4 changes: 2 additions & 2 deletions Singer.API/ClientApp/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<app-nav-menu (logoutEvent)="onLogout()"></app-nav-menu>
<app-nav-menu (logoutEvent)="logout()"></app-nav-menu>
<div class="container">
<router-outlet></router-outlet>
<router-outlet *ngIf="!isIframe"></router-outlet>
</div>
163 changes: 153 additions & 10 deletions Singer.API/ClientApp/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,163 @@
import { Component } from '@angular/core';
import { AuthService } from './modules/core/services/auth.service';
import { Router } from '@angular/router';
import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';

import {
AuthenticationResult,
EventMessage,
EventType,
InteractionStatus,
InteractionType,
PopupRequest,
RedirectRequest,
} from '@azure/msal-browser';
import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';

import { B2PCPolicyStore } from './modules/core/services/b2cpolicy.state.store';

interface Payload extends AuthenticationResult {
idTokenClaims: {
tfp?: string;
};
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
title = 'Singer';
export class AppComponent implements OnInit, OnDestroy {
title = 'Microsoft identity platform';
isIframe = false;
loginDisplay = false;
private readonly _destroying$ = new Subject<void>();

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

ngOnInit(): void {
this.isIframe = window !== window.parent && !window.opener;
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.inProgress$
.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None),
takeUntil(this._destroying$)
)
.subscribe(() => {
this.setLoginDisplay();
this.checkAndSetActiveAccount();
});

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: Payload = <AuthenticationResult>result.payload;

/**
* For the purpose of setting an active account for UI update, we want to consider only the auth response resulting
* from SUSI flow. "tfp" claim in the id token tells us the policy (NOTE: legacy policies may use "acr" instead of "tfp").
* To learn more about B2C tokens, visit https://docs.microsoft.com/en-us/azure/active-directory-b2c/tokens-overview
*/
if (payload.idTokenClaims['tfp'] === this.b2cPolicyStateStore.getPolicies().b2cPolicyNames.editProfile) {
window.alert('Profile has been updated successfully. \nPlease sign-in again.');
return this.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) => {
// Add your auth error handling logic here
});
}

constructor(private authService: AuthService, private router: Router) {}
setLoginDisplay() {
this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
}

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]);
}
}

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() {
if (this.msalGuardConfig.interactionType === InteractionType.Popup) {
this.authService.logoutPopup({
mainWindowRedirectUri: '/',
});
} else {
this.authService.logoutRedirect();
}
}

editProfile() {
let editProfileFlowRequest: RedirectRequest | PopupRequest = {
authority: this.b2cPolicyStateStore.getPolicies().authorities.editProfile.authority,
scopes: [],
};

this.login(editProfileFlowRequest);
}

onLogout() {
this.router.navigateByUrl('/dashboard').then(() => {
this.authService.logout();
});
// unsubscribe to events when component is destroyed
ngOnDestroy(): void {
this._destroying$.next(undefined);
this._destroying$.complete();
}
}
64 changes: 11 additions & 53 deletions Singer.API/ClientApp/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

// import { ApplicationInsightsModule, AppInsightsService } from '@markpieszak/ng-application-insights';
import { AppComponent } from './app.component';
import { MaterialModule } from './material.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppRoutingModule } from './app-routing.module';
import { JwtModule, JwtHelperService } from '@auth0/angular-jwt';
import { MainComponent } from './main.component';
import { AuthService } from './modules/core/services/auth.service';
import { AuthGuard } from './modules/core/services/auth.guard';
import { AuthInterceptor } from './modules/core/services/auth-interceptor';

import { NavMenuComponent } from './modules/core/components/nav-menu/nav-menu.component';
import { NativeDateModule, MAT_DATE_LOCALE, DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
import { AdminModule } from './modules/admin/admin.module';
import { MomentDateAdapter } from '@angular/material-moment-adapter';
import { ConfigurationService } from './modules/core/services/clientconfiguration.service';
import { ApplicationInsightsService } from './modules/core/services/applicationinsights.service';
import { registerLocaleData } from '@angular/common';
import localeBe from '@angular/common/locales/be';
import { CalendarModule } from 'angular-calendar';
import { MsalGuardConfiguration, MsalRedirectComponent } from '@azure/msal-angular';
import { IPublicClientApplication, InteractionType, PublicClientApplication } from '@azure/msal-browser';
import { MSALStateStore } from './modules/core/services/msal.state.store';
import { MsalConfigDynamicModule } from './msal-config.dynamic.module';

// Import locale settings for Belgium
registerLocaleData(localeBe);

export function tokenGetter(): string {
return localStorage.getItem('token');
}
export const MY_FORMATS = {
parse: {
dateInput: 'D-MM-YYYY',
Expand All @@ -41,58 +36,21 @@ export const MY_FORMATS = {
@NgModule({
declarations: [AppComponent, MainComponent, NavMenuComponent],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
AdminModule,
HttpClientModule,
MaterialModule,
BrowserAnimationsModule,
NativeDateModule,
JwtModule.forRoot({
config: {
tokenGetter: tokenGetter,
},
}),
// ApplicationInsightsModule.forRoot({
// instrumentationKeySetLater: true,
// }),
MsalConfigDynamicModule.forRoot(),
],
providers: [
JwtHelperService,
AuthGuard,
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true,
},
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [AuthService, ConfigurationService, ApplicationInsightsService],
multi: true,
},
BrowserAnimationsModule,
{ provide: MAT_DATE_LOCALE, useValue: 'nl-BE' },
{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
{ provide: DateAdapter, useClass: MomentDateAdapter },
// AppInsightsService,
],
bootstrap: [AppComponent],
bootstrap: [AppComponent, MsalRedirectComponent],
})
export class AppModule {}

export function initializeApp(
authService: AuthService,
configurationService: ConfigurationService,
applicationInsightService: ApplicationInsightsService
) {
return () => {
configurationService
.load()
.toPromise()
.then(() => {
authService.restore();
applicationInsightService.init();
});
};
}
2 changes: 1 addition & 1 deletion Singer.API/ClientApp/src/app/main.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./main.component.css'],
})
export class MainComponent implements OnInit {
constructor() {}
loginDisplay = false;

ngOnInit() {}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Routes, RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { AuthGuard } from '../core/services/auth.guard';

import { CareUserOverviewComponent } from './components/careusers/careuser-overview/care-user-overview.component';
import { AdminOverviewComponent } from './components/admin-users/admin-overview/admin-overview.component';
import { LegalguardianOverviewComponent } from './components/legalguardians/legalguardian-overview/legalguardian-overview.component';
Expand All @@ -15,7 +15,7 @@ const routes: Routes = [
{
path: 'admin',
component: MainComponent,
canActivate: [AuthGuard],
canActivate: [],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a todo to make sure we add a substitute here?

children: [
{
path: 'beheerders',
Expand Down
Loading
Loading