Phase-3a step per ADR-0020 §"Confirmation" item 4 (entry route +
admin shell). Wires the existing `feature-auth` library against the
distinct admin OIDC routes (`/api/admin/auth/*`) the BFF exposes
since PR #129, ships a lean header/sidebar/footer chrome with an
"Admin" badge so an internal user can never mistake the surface for
portal-shell, and gives the landing page a self-test panel that
confirms the auth chain end-to-end as soon as an `admin` Entra role
gets assigned.
Lib changes (libs/feature/auth)
- New AUTH_PATH_PREFIX injection token. Default factory returns
`/auth` so portal-shell-shaped hosts keep working without an
explicit provider. Admin hosts override with `/admin/auth`.
- AuthService composes URLs as `${bffBaseUrl}${pathPrefix}/{me,login,
logout}`. The interceptors are unchanged — they only care about
the BFF base URL.
portal-admin wiring
- environment.ts: same shape as portal-shell, same BFF base URL
(both SPAs talk to one BFF per ADR-0020 §"Where does the admin
app live"), same CSRF cookie name in v1.
- app.config.ts: HttpClient with the standard interceptor chain
(credentials → csrf → unauthorized), AUTH_BFF_BASE_URL from env,
AUTH_PATH_PREFIX = '/admin/auth', AUTH_CSRF_COOKIE_NAME from env.
Admin shell components
- AdminHeader: APF wordmark + persistent "Admin" badge + auth widget.
No global search / notifications / help cluster — admins land on
tabular workloads, not discovery. Per ADR-0020 §"UX style is
data-dense".
- AdminSidebar: static menu listing the four ADR-0020 v1 modules.
Audit log is a live router link (target of PR 8); the others are
aria-disabled placeholders with a "Soon" badge so the navigation
shape is visible even before they ship.
- AdminFooter: copyright + persistent "Admin surface" tag — the
badge stays in view for long workloads where the header may have
scrolled off.
Home page
- Auth self-test panel: signed-in payload (display name, tenant,
oid) when authenticated, Sign in button when anonymous, error
state when the BFF is unreachable.
- Roadmap list quoting ADR-0020's v1 catalogue so a returning
contributor sees what's pending without grepping.
Notes
- LayoutStateService isn't consumed yet (no collapse toggle in v1),
but the theme preference still threads through because both apps
read the same localStorage key — toggle in portal-shell, see it
honoured in portal-admin.
- Strings are plain English in v1. The admin app's $localize plumbing
is wired but adding markers to the shell chrome here would require
regenerating messages.fr.xlf and i18nMissingTranslation=error fails
the prod build on every gap. Full i18n is its own follow-up.
- v1 reuses the shared `portal_csrf` cookie name. A user with both
portals open could see CSRF cookie overwrites on session creation;
splitting to `portal_admin_csrf` is a follow-up if the pattern
becomes common.
- Build verified locally: 86.65 KB gzip initial / 300 KB budget,
4.46 KB CSS / 150 KB budget.
Tests: +15 specs (AdminHeader 5, AdminSidebar 3, AdminFooter 2,
Home 4, App 1 expanded). 1 new test in feature-auth AuthService for
the AUTH_PATH_PREFIX override path.