Files
SharepointToolbox-Web/Components/Shared/AppInitializer.razor
T
kawa c23039efa1 Fix stuck-on-loading after sign-in; enable HTTP/LAN local login
The app stuck on "Chargement…" after sign-in because the interactive
Blazor circuit came up anonymous: no auth cookie reached this origin.
Root cause was the deployment (plain HTTP on an IP, http://host:8080),
which Microsoft OIDC cannot serve — Entra forbids http redirect URIs for
non-localhost hosts, so the sign-in cookie never lands on the origin.

Changes:
- ForwardedHeaders (X-Forwarded-Proto/For) so that behind a TLS proxy the
  app sees the real https scheme, builds a matching OIDC redirect_uri, and
  sets the auth cookie Secure. Proxy IP unknown in-container → known
  proxy/network restrictions cleared.
- First-run bootstrap: seed a local admin (Bootstrap__AdminEmail /
  Bootstrap__AdminPassword) when that email has no account, so HTTP/LAN
  deployments that can't use OIDC can sign in via the local form. Idempotent.
- OIDC SaveTokens=false: the cookie-stored access/id/refresh tokens were
  never read (SharePoint/Graph auth uses the separate connect-flow + cert
  paths). Dropping them keeps the auth cookie small/unchunked.
- AppInitializer now logs which branch leaves UserContext unseeded
  (unauthenticated principal / missing claim / no user row) instead of
  failing silently — this is what surfaced the anonymous-circuit cause.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 15:46:53 +02:00

43 lines
1.7 KiB
Plaintext

@inject AuthenticationStateProvider AuthProvider
@inject IUserService UserService
@inject IUserContextAccessor UserContext
@inject ILogger<AppInitializer> Logger
@using Microsoft.AspNetCore.Components.Authorization
@using SharepointToolbox.Web.Services.Auth
@using SharepointToolbox.Web.Services.Session
@* Invisible component. Run once per circuit to seed IUserContextAccessor. *@
@code {
protected override async Task OnInitializedAsync()
{
var state = await AuthProvider.GetAuthenticationStateAsync();
var principal = state.User;
if (principal.Identity?.IsAuthenticated != true)
{
Logger.LogWarning("AppInitializer: circuit principal NOT authenticated; UserContext left unseeded → page stays on loading.");
return;
}
var email = principal.FindFirst("preferred_username")?.Value
?? principal.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value;
if (string.IsNullOrEmpty(email))
{
var claims = string.Join(", ", principal.Claims.Select(c => $"{c.Type}={c.Value}"));
Logger.LogWarning("AppInitializer: authenticated but no preferred_username/email claim. Claims present: [{Claims}]", claims);
return;
}
var user = await UserService.GetByEmailAsync(email);
if (user is null)
{
Logger.LogWarning("AppInitializer: no user row for email '{Email}' — provisioning did not persist a matching record.", email);
return;
}
Logger.LogInformation("AppInitializer: seeded UserContext for '{Email}' (role {Role}).", user.Email, user.Role);
UserContext.Initialize(user);
}
}