feat(portal-bff): msal confidential client provider in AuthModule
CI / commits (pull_request) Successful in 2m13s
CI / scan (pull_request) Successful in 2m35s
CI / check (pull_request) Successful in 3m23s
CI / a11y (pull_request) Successful in 1m40s
CI / perf (pull_request) Successful in 4m46s

Second step of ADR-0009 wiring. AuthModule now exposes the
`@azure/msal-node` confidential client alongside the parsed Entra
config — the building block the upcoming OIDC routes inject to issue
the auth-code URL, exchange the callback code for tokens, and
acquire downstream tokens on behalf of the user.

What lands:

- `@azure/msal-node` added as a direct dependency (^5.2.1).
- `apps/portal-bff/src/auth/msal-client.token.ts` — `MSAL_CLIENT`
  string token + `ConfidentialClientApplication` type re-export.
  Mirrors the `ENTRA_CONFIG` token shape from PR #102.
- AuthModule grows a factory provider for `MSAL_CLIENT`:
  - Injects `ENTRA_CONFIG` + nestjs-pino `Logger`.
  - Builds a `ConfidentialClientApplication` with `clientId`,
    `authority`, `clientSecret` from the parsed config.
  - Wires `system.loggerOptions.loggerCallback` to forward MSAL's
    internal log lines into the Pino stream (per ADR-0012) — Error
    → logger.error, Warning → logger.warn, Verbose / Trace →
    logger.debug, Info → logger.log. PII logging is disabled by
    default so tokens / user identifiers never leak into our
    structured log records.
  - Sets MSAL's `logLevel` to Info — Pino's own threshold
    re-filters from there.
  - All MSAL log lines carry the `msal` Pino context for easy
    isolation in log queries.
- `AuthModule.exports` extended to include `MSAL_CLIENT`.

Verification:

- `nx run-many -t lint test build --projects=portal-bff` — green.
- 30/30 specs (was 29; +1 covering MSAL client construction).
- New spec imports `nestjs-pino`'s `LoggerModule.forRoot({ pinoHttp:
  { level: 'silent' } })` to provide the same Logger the production
  app supplies via `ObservabilityModule`, without flooding test
  stdout. Two tests assert the provider tree resolves correctly
  (ENTRA_CONFIG + MSAL_CLIENT) and one re-checks the missing-env
  failure mode still propagates through the new factory.

Construction is cheap — MSAL Node defers authority discovery to the
first auth call — so the client is built eagerly at module init.
The factory is injection-only; no MSAL methods get invoked yet.
Routes land in the next PR.
This commit is contained in:
Julien Gautier
2026-05-12 09:58:59 +02:00
parent abd651b697
commit aeda239b1c
5 changed files with 199 additions and 16 deletions
+99
View File
@@ -41,6 +41,9 @@ importers:
'@angular/router':
specifier: ~21.2.0
version: 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)
'@azure/msal-node':
specifier: ^5.2.1
version: 5.2.1
'@nestjs/common':
specifier: ^11.0.0
version: 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -685,6 +688,14 @@ packages:
'@asamuzakjp/nwsapi@2.3.9':
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
'@azure/msal-common@16.6.1':
resolution: {integrity: sha512-VxKdEtUwDuLD0F1hOQP7kye0YadZxFJfv37Em440geEf/w9uggKnHpRrqwZJOdxmPUOdhZ9kyRtKuAJW8wUcRg==}
engines: {node: '>=0.8.0'}
'@azure/msal-node@5.2.1':
resolution: {integrity: sha512-tmQiQ2HvtzaeLqYGy3BemiPOSGPY4wCy1IW5zDWITKSs/s35WEd7Zij/hCxvUdAOzj6U3qnyaGbYXY91ortFEQ==}
engines: {node: '>=20'}
'@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
@@ -5097,6 +5108,9 @@ packages:
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
buffer-equal-constant-time@1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -5756,6 +5770,9 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
ecdsa-sig-formatter@1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
@@ -7129,6 +7146,16 @@ packages:
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
engines: {'0': node >= 0.2.0}
jsonwebtoken@9.0.3:
resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==}
engines: {node: '>=12', npm: '>=6'}
jwa@2.0.1:
resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==}
jws@4.0.1:
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -7338,12 +7365,33 @@ packages:
lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
lodash.includes@4.3.0:
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
lodash.isboolean@3.0.3:
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
lodash.isinteger@4.0.4:
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
lodash.isnumber@3.0.3:
resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
lodash.isstring@4.0.1:
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
lodash.memoize@4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
lodash.once@4.1.1:
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
lodash.uniq@4.5.0:
resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
@@ -10531,6 +10579,13 @@ snapshots:
'@asamuzakjp/nwsapi@2.3.9': {}
'@azure/msal-common@16.6.1': {}
'@azure/msal-node@5.2.1':
dependencies:
'@azure/msal-common': 16.6.1
jsonwebtoken: 9.0.3
'@babel/code-frame@7.29.0':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
@@ -15699,6 +15754,8 @@ snapshots:
buffer-crc32@0.2.13: {}
buffer-equal-constant-time@1.0.1: {}
buffer-from@1.1.2: {}
buffer@5.7.1:
@@ -16350,6 +16407,10 @@ snapshots:
eastasianwidth@0.2.0: {}
ecdsa-sig-formatter@1.0.11:
dependencies:
safe-buffer: 5.2.1
ee-first@1.1.1: {}
effect@3.21.0:
@@ -18175,6 +18236,30 @@ snapshots:
jsonparse@1.3.1: {}
jsonwebtoken@9.0.3:
dependencies:
jws: 4.0.1
lodash.includes: 4.3.0
lodash.isboolean: 3.0.3
lodash.isinteger: 4.0.4
lodash.isnumber: 3.0.3
lodash.isplainobject: 4.0.6
lodash.isstring: 4.0.1
lodash.once: 4.1.1
ms: 2.1.3
semver: 7.8.0
jwa@2.0.1:
dependencies:
buffer-equal-constant-time: 1.0.1
ecdsa-sig-formatter: 1.0.11
safe-buffer: 5.2.1
jws@4.0.1:
dependencies:
jwa: 2.0.1
safe-buffer: 5.2.1
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -18418,10 +18503,24 @@ snapshots:
lodash.debounce@4.0.8: {}
lodash.includes@4.3.0: {}
lodash.isboolean@3.0.3: {}
lodash.isinteger@4.0.4: {}
lodash.isnumber@3.0.3: {}
lodash.isplainobject@4.0.6: {}
lodash.isstring@4.0.1: {}
lodash.memoize@4.1.2: {}
lodash.merge@4.6.2: {}
lodash.once@4.1.1: {}
lodash.uniq@4.5.0: {}
lodash@4.18.1: {}