Skip to content

Commit

Permalink
feat: configurable B2C parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
BerendWouters committed Jan 30, 2024
1 parent 32f94ca commit a1c2163
Show file tree
Hide file tree
Showing 12 changed files with 69 additions and 60 deletions.
4 changes: 2 additions & 2 deletions Singer.API/ClientApp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion 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-issue-msal.1584",
"version": "0.1.0-issue-msal.1585",
"scripts": {
"ng": "ng",
"start": "ng serve",
Expand Down
4 changes: 2 additions & 2 deletions Singer.API/ClientApp/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
RedirectRequest,
} from '@azure/msal-browser';
import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';
import { b2cPolicyNames } from './modules/core/services/auth-config';

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

interface Payload extends AuthenticationResult {
Expand Down Expand Up @@ -69,7 +69,7 @@ export class AppComponent implements OnInit, OnDestroy {
* 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'] === b2cPolicyNames.editProfile) {
if (payload.idTokenClaims['tfp'] === this.b2cPolicyStateStore.getPolicies().b2cPolicyNames.editProfile) {
window.alert('Profile has been updated successfully. \nPlease sign-in again.');
return this.logout();
}
Expand Down
26 changes: 14 additions & 12 deletions Singer.API/ClientApp/src/app/modules/core/services/auth-config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
export const isIE =
window.navigator.userAgent.indexOf('MSIE ') > -1 || window.navigator.userAgent.indexOf('Trident/') > -1;
export const b2cPolicyNames = {
signUpSignIn: '',
editProfile: '',
resetPassword: '',
};

export interface IB2CPolicyNames {
signUpSignIn: string;
editProfile: string;
resetPassword: string;
}

export interface IB2CPolicies {
authorities: {
Expand All @@ -19,16 +20,17 @@ export interface IB2CPolicies {
};
};
authorityDomain: string;
b2cPolicyNames: IB2CPolicyNames;
}

export const endpoint = 'https://localhost:5001/api/';
export const endpoint = 'https://localhost:5001/';

export const protectedResources = {
default: {
endpoint: `${endpoint}*`,
scopes: ['https://vzwstijn.onmicrosoft.com/4ea3a07f-5db9-4290-b930-88806df40e9d/Events.Read'],
},
};
export interface IProtectedResources {
[name: string]: {
endpoint: string;
scopes: string[];
};
}

export const loginRequest = {
scopes: [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Injectable } from '@angular/core';
import { ObservableStore } from '@codewithdan/observable-store';
import { IB2CPolicies, b2cPolicyNames } from './auth-config';
import { IB2CPolicies, IB2CPolicyNames } from './auth-config';

@Injectable({ providedIn: 'root' })
export class B2PCPolicyStore extends ObservableStore<IB2CPolicies> {
constructor() {
super({});
}

load(authority: string, tenant: string) {
load(authority: string, tenant: string, b2cPolicyNames: IB2CPolicyNames) {
this.setState({
authorities: {
signUpSignIn: {
Expand All @@ -21,8 +21,8 @@ export class B2PCPolicyStore extends ObservableStore<IB2CPolicies> {
authority: `https://${authority}/${tenant}.onmicrosoft.com/${b2cPolicyNames.resetPassword}`,
},
},
// authorityDomain: 'vzwstijn.b2clogin.com',
authorityDomain: authority,
b2cPolicyNames: b2cPolicyNames,
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { IB2CPolicyNames, IProtectedResources } from './auth-config';
@Injectable({
providedIn: 'root',
})
Expand All @@ -25,4 +23,6 @@ export interface Configuration {
tenant: string;
client_id: string;
applicationinsights_intrumentationkey: string;
b2cPolicies: IB2CPolicyNames;
protectedResources: IProtectedResources;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RegistrationOverviewDTO } from '../../DTOs/registration.dto';
import { GenericService } from '../generic-service';
import { HttpClient } from '@angular/common/http';
import { RegistrationSearchDTO } from '../../DTOs/registration.dto';
import { endpoint, protectedResources } from '../auth-config';
import { endpoint } from '../auth-config';

@Injectable({
providedIn: 'root',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class PendingRegistrationsService extends GenericService<Registration, Re
};
}
constructor(protected httpClient: HttpClient) {
super(`${endpoint}/event/registrations/status/pending`);
super(`${endpoint}/api/event/registrations/status/pending`);
}

advancedSearch(): Observable<PaginationDTO<RegistrationDTO>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,27 @@ export class SingerEventsProxy {
.set('pageIndex', pageIndex.toString())
.set('pageSize', pageSize.toString())
.set('filter', filter);
return this.apiService.get('api/event', searchParams).pipe(map(res => res));
return this.apiService.get('api/event', searchParams).pipe(map((res) => res));
}

updateSingerEvents(id: string, updateSingerEventDTO: UpdateSingerEventDTO) {
return this.apiService.put(`api/event/${id}`, updateSingerEventDTO).pipe(map(res => res));
return this.apiService.put(`api/event/${id}`, updateSingerEventDTO).pipe(map((res) => res));
}

createSingerEvents(createSingerEventDTO: CreateSingerEventDTO): Observable<SingerEventDTO> {
return this.apiService.post('api/event', createSingerEventDTO).pipe(map(res => res));
return this.apiService.post('api/event', createSingerEventDTO).pipe(map((res) => res));
}

deleteSingerEvent(eventId: string): Observable<any> {
return this.apiService.delete(`api/event/${eventId}`).pipe(map(res => res));
return this.apiService.delete(`api/event/${eventId}`).pipe(map((res) => res));
}

getEventRegisterDetails(eventId: string): Observable<EventRegisterDetails> {
return this.apiService.get(`api/event/${eventId}/geteventregisterdetails`).pipe(map(res => res));
return this.apiService.get(`api/event/${eventId}/geteventregisterdetails`).pipe(map((res) => res));
}

registerCareUserOnEvent(eventId: string, dto: CreateRegistrationDTO): Observable<RegistrationDTO[]> {
return this.apiService.post(`api/event/${eventId}/registrations`, dto).pipe(map(res => res));
return this.apiService.post(`api/event/${eventId}/registrations`, dto).pipe(map((res) => res));
}

registerCareUserOnEventSlot(
Expand All @@ -65,11 +65,11 @@ export class SingerEventsProxy {
): Observable<RegistrationDTO> {
return this.apiService
.post(`api/event/${eventId}/eventslot/${eventSlotId}/registrations`, dto)
.pipe(map(res => res));
.pipe(map((res) => res));
}

isUserRegisteredForEvent(eventId: string, careUserId: string): Observable<UserRegisteredDTO> {
return this.apiService.get(`api/event/${eventId}/isuserregistered/${careUserId}`).pipe(map(res => res));
return this.apiService.get(`api/event/${eventId}/isuserregistered/${careUserId}`).pipe(map((res) => res));
}

getRegistrations(
Expand All @@ -86,7 +86,7 @@ export class SingerEventsProxy {
.set('pageIndex', pageIndex.toString())
.set('pageSize', pageSize.toString())
.set('filter', filter);
return this.apiService.get(`api/event/${eventId}/registrations`, searchParams).pipe(map(res => res));
return this.apiService.get(`api/event/${eventId}/registrations`, searchParams).pipe(map((res) => res));
}

getPublicEvents(eventFilterData: EventFilterParameters): Observable<EventDescriptionDTO[]> {
Expand All @@ -97,7 +97,7 @@ export class SingerEventsProxy {
allowedAgeGroups: eventFilterData.allowedAgeGroups,
text: eventFilterData.text,
};
return this.apiService.post('api/event/search', filterParams).pipe(map(res => res));
return this.apiService.post('api/event/search', filterParams).pipe(map((res) => res));
}

downloadEventSlotRegistartionCsv(eventId: string, eventSlotId: string): Observable<Blob> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
} from '@azure/msal-browser';
import { filter, takeUntil } from 'rxjs/operators';
import { B2PCPolicyStore } from 'src/app/modules/core/services/b2cpolicy.state.store';
import { b2cPolicyNames } from 'src/app/modules/core/services/auth-config';

type IdTokenClaimsWithPolicyId = IdTokenClaims & {
acr?: string;
Expand Down Expand Up @@ -88,7 +87,10 @@ export class AuthComponent implements OnInit {
let payload = result.payload as AuthenticationResult;
let idtoken = payload.idTokenClaims as IdTokenClaimsWithPolicyId;

if (idtoken.acr === b2cPolicyNames.signUpSignIn || idtoken.tfp === b2cPolicyNames.signUpSignIn) {
if (
idtoken.acr === this.b2cPolicyStore.getPolicies().b2cPolicyNames.signUpSignIn ||
idtoken.tfp === this.b2cPolicyStore.getPolicies().b2cPolicyNames.signUpSignIn
) {
this.authService.instance.setActiveAccount(payload.account);
}

Expand All @@ -97,16 +99,21 @@ export class AuthComponent implements OnInit {
* from SUSI flow. "acr" claim in the id token tells us the policy (NOTE: newer policies may use the "tfp" claim instead).
* To learn more about B2C tokens, visit https://docs.microsoft.com/en-us/azure/active-directory-b2c/tokens-overview
*/
if (idtoken.acr === b2cPolicyNames.editProfile || idtoken.tfp === b2cPolicyNames.editProfile) {
if (
idtoken.acr === this.b2cPolicyStore.getPolicies().b2cPolicyNames.editProfile ||
idtoken.tfp === this.b2cPolicyStore.getPolicies().b2cPolicyNames.editProfile
) {
// retrieve the account from initial sing-in to the app
const originalSignInAccount = this.authService.instance
.getAllAccounts()
.find(
(account: AccountInfo) =>
account.idTokenClaims?.oid === idtoken.oid &&
account.idTokenClaims?.sub === idtoken.sub &&
((account.idTokenClaims as IdTokenClaimsWithPolicyId).acr === b2cPolicyNames.signUpSignIn ||
(account.idTokenClaims as IdTokenClaimsWithPolicyId).tfp === b2cPolicyNames.signUpSignIn)
((account.idTokenClaims as IdTokenClaimsWithPolicyId).acr ===
this.b2cPolicyStore.getPolicies().b2cPolicyNames.signUpSignIn ||
(account.idTokenClaims as IdTokenClaimsWithPolicyId).tfp ===
this.b2cPolicyStore.getPolicies().b2cPolicyNames.signUpSignIn)
);

let signUpSignInFlowRequest: SsoSilentRequest = {
Expand All @@ -125,7 +132,10 @@ export class AuthComponent implements OnInit {
* you can replace the code below with the same pattern used for handling the return from
* profile edit flow (see above ln. 74-92).
*/
if (idtoken.acr === b2cPolicyNames.resetPassword || idtoken.tfp === b2cPolicyNames.resetPassword) {
if (
idtoken.acr === this.b2cPolicyStore.getPolicies().b2cPolicyNames.resetPassword ||
idtoken.tfp === this.b2cPolicyStore.getPolicies().b2cPolicyNames.resetPassword
) {
let signUpSignInFlowRequest: RedirectRequest | PopupRequest = {
authority: this.b2cPolicyStore.getPolicies().authorities.signUpSignIn.authority,
prompt: PromptValue.LOGIN, // force user to reauthenticate with their new password
Expand Down
22 changes: 4 additions & 18 deletions Singer.API/ClientApp/src/app/msal-config.dynamic.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
} from '@azure/msal-angular';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { MSALStateStore } from './modules/core/services/msal.state.store';
import { protectedResources } from './modules/core/services/auth-config';
import { ApplicationInsightsService } from './modules/core/services/applicationinsights.service';
import { ConfigurationService } from './modules/core/services/clientconfiguration.service';
import { B2PCPolicyStore } from './modules/core/services/b2cpolicy.state.store';
Expand All @@ -40,7 +39,8 @@ export function MSALInstanceFactory(config: MSALStateStore): IPublicClientApplic
});
}

export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
export function MSALInterceptorConfigFactory(configurationService: ConfigurationService): MsalInterceptorConfiguration {
const protectedResources = configurationService.configuration.protectedResources;
const protectedResourceMap = new Map<string, Array<string | ProtectedResourceScopes> | null>();
protectedResourceMap.set(protectedResources.default.endpoint, [...protectedResources.default.scopes]);
// protectedResourceMap.set(protectedResources.registrationsList.endpoint, [
Expand Down Expand Up @@ -92,6 +92,7 @@ export class MsalConfigDynamicModule {
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: MSALInterceptorConfigFactory,
deps: [ConfigurationService],
},
MsalService,
MsalGuard,
Expand All @@ -106,21 +107,6 @@ export class MsalConfigDynamicModule {
}
}

export function initializeApp(
configurationService: ConfigurationService,
applicationInsightService: ApplicationInsightsService,
msalStateStore: MSALStateStore,
b2PCPolicyStore: B2PCPolicyStore
) {
return () => {
configurationService.load().then((c) => {
b2PCPolicyStore.load(c.authority, c.tenant);
msalStateStore.load(b2PCPolicyStore.getPolicies(), c.client_id, '/');
applicationInsightService.init();
});
};
}

export function initializerFactory(
env: ConfigurationService,
msalStateStore: MSALStateStore,
Expand All @@ -132,7 +118,7 @@ export function initializerFactory(
msalStateStore.load(x, value.client_id, '/');
}
});
b2cPolicyStateStore.load(value.authority, value.tenant);
b2cPolicyStateStore.load(value.authority, value.tenant, value.b2cPolicies);

console.log('finished getting configurations dynamically.');
});
Expand Down
13 changes: 12 additions & 1 deletion Singer.API/ClientApp/src/assets/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,16 @@
"tenant": "",
"authority": "",
"client_id": "",
"applicationinsights_intrumentationkey": ""
"applicationinsights_intrumentationkey": "",
"b2cPolicies": {
"signUpSignIn": "",
"editProfile": "",
"resetPassword": ""
},
"protectedResources": {
"default": {
"endpoint": "",
"scopes": []
}
}
}

0 comments on commit a1c2163

Please sign in to comment.