Skip to content

Upgrade AdHocReporting.BlazorWasm (Server/Client/Shared) to .NET 8#37

Open
devin-ai-integration[bot] wants to merge 5 commits into
masterfrom
devin/1777991077-net8-blazorwasm-adhoc
Open

Upgrade AdHocReporting.BlazorWasm (Server/Client/Shared) to .NET 8#37
devin-ai-integration[bot] wants to merge 5 commits into
masterfrom
devin/1777991077-net8-blazorwasm-adhoc

Conversation

@devin-ai-integration
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot commented May 5, 2026

Summary

Upgrades the three Blazor WebAssembly AdHocReporting sample projects from .NET 6 to .NET 8 and replaces the Microsoft.AspNetCore.ApiAuthorization.IdentityServer auth stack (removed in .NET 8) with the standard ASP.NET Core Identity cookie auth (Razor Pages UI) shipped in Microsoft.AspNetCore.Identity.UI.

Projects modified

Project TFM Key package version changes
AspNetCore/Blazor/AdHocReporting.BlazorWasm/Server/EqDemo.BlazorWasm.AdhocReporting.Server.csproj net6.0net8.0 See "Server" below
AspNetCore/Blazor/AdHocReporting.BlazorWasm/Client/EqDemo.BlazorWasm.AdhocReporting.Client.csproj net6.0net8.0 See "Client" below
AspNetCore/Blazor/AdHocReporting.BlazorWasm/Shared/EqDemo.BlazorWasm.AdhocReporting.Shared.csproj net6.0net8.0 TFM only

Server (Server/EqDemo.BlazorWasm.AdhocReporting.Server.csproj)

Package Before After
Microsoft.AspNetCore.Components.WebAssembly.Server 6.0.1 8.0.11
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore 6.0.1 8.0.11
Microsoft.AspNetCore.Identity.EntityFrameworkCore 6.0.1 8.0.11
Microsoft.AspNetCore.Identity.UI 6.0.1 8.0.11
Microsoft.EntityFrameworkCore.Tools 6.0.1 8.0.11
Microsoft.EntityFrameworkCore.SqlServer 6.0.1 8.0.11
Microsoft.EntityFrameworkCore.Sqlite 6.0.1 8.0.11
Microsoft.AspNetCore.Authentication.JwtBearer new 8.0.11
Microsoft.Data.SqlClient 2.1.7 5.2.2
Microsoft.IdentityModel.JsonWebTokens 6.34.0 7.6.0
System.IdentityModel.Tokens.Jwt 6.34.0 7.6.0
Microsoft.AspNetCore.ApiAuthorization.IdentityServer 6.0.1 removed (no longer published for .NET 8)
Microsoft.VisualStudio.Web.CodeGeneration.Design 6.0.1 removed (not needed at runtime)
System.Data.SqlClient 4.8.6 removed (provided by BCL)
System.Drawing.Common 4.7.2 removed (provided by BCL)
System.Net.Http 4.3.4 removed (provided by BCL)
System.Text.RegularExpressions 4.3.1 removed (provided by BCL)

Korzh.* / EasyData / NuGet.* packages are unchanged per the upgrade scope.

Client (Client/EqDemo.BlazorWasm.AdhocReporting.Client.csproj)

Package Before After
Microsoft.AspNetCore.Components 6.0.25 8.0.11
Microsoft.AspNetCore.Components.WebAssembly 6.0.1 8.0.11
Microsoft.AspNetCore.Components.WebAssembly.DevServer 6.0.1 8.0.11
Microsoft.AspNetCore.Components.WebAssembly.Authentication 6.0.1 8.0.11
Microsoft.Extensions.Http 6.0.0 8.0.1

Shared (Shared/EqDemo.BlazorWasm.AdhocReporting.Shared.csproj)

TFM bump only (net6.0net8.0); no package references.

Code changes

Server

  • Server/Program.cs — removed AddIdentityServer().AddApiAuthorization<…>(…), AddAuthentication().AddIdentityServerJwt(), app.UseIdentityServer(), and the JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role") line that supported the IdentityServer JWT pipeline. Cookie-based auth via AddDefaultIdentity<ApplicationUser> is now the sole auth provider.
  • Server/Program.cs — explicitly registered UserClaimsPrincipalFactory<ApplicationUser, IdentityRole> after AddEntityFrameworkStores<AppDbContext>(). AddIdentityCore (called inside AddDefaultIdentity) pre-registers the role-blind UserClaimsPrincipalFactory<TUser> via TryAddScoped, which silently wins over the role-aware factory that AddRoles<IdentityRole>() tries to add — without this override User.IsInRole("eq-manager") would always be false, breaking the Reports.razor manager-only UI and any server-side EasyQuery role checks.
  • Server/Program.cs — added a small app.MapGet("/_auth/me", ...) minimal API endpoint that returns the current cookie-authenticated user's claims as JSON. The Blazor WASM client's HostAuthenticationStateProvider consumes this to populate the <CascadingAuthenticationState> / <AuthorizeView> cascade.
  • Server/Data/AppDbContext.cs — switched the base class from ApiAuthorizationDbContext<ApplicationUser> (provided by the removed Microsoft.AspNetCore.ApiAuthorization.IdentityServer / Duende.IdentityServer.EntityFramework.Options) to IdentityDbContext<ApplicationUser> from Microsoft.AspNetCore.Identity.EntityFrameworkCore. Constructor signature simplified to take DbContextOptions<AppDbContext>.
  • Server/Controllers/OidcConfigurationController.csdeleted. The controller existed solely to expose IdentityServer client config via IClientRequestParametersProvider; that type lives in the removed package.
  • Server/appsettings.json — removed the IdentityServer.Clients block (no longer read).

Client

  • Client/Program.cs — removed BaseAddressAuthorizationMessageHandler registration and AddApiAuthorization() (the OIDC client flow no longer has a backend to talk to); replaced with AddAuthorizationCore() plus a custom AuthenticationStateProvider registration so <CascadingAuthenticationState>, <AuthorizeRouteView>, and <AuthorizeView> continue to resolve at runtime.
  • Client/HostAuthenticationStateProvider.csnew file. Resolves the current user's auth state by GETing /_auth/me (sent with the Identity cookie, since same-origin) and projecting the returned claims into a ClaimsPrincipal. Returns anonymous on 401/403/network errors. This replaces the OIDC RemoteAuthenticationService-backed provider that previously came with AddApiAuthorization().
  • Client/Pages/Authentication.razor — removed RemoteAuthenticatorView (which depended on the OIDC RemoteAuthenticationService) and replaced with a small redirect that maps the legacy /authentication/{login,register,logout,profile} URLs onto the Identity/Account/{Login,Register,Logout,Manage} Razor Pages via NavigationManager.NavigateTo(..., forceLoad: true).
  • Client/Shared/LoginDisplay.razor — kept the original authentication/{action} hrefs (rather than linking directly to Identity/Account/...) so they route through Authentication.razor and trigger a forceLoad server-side navigation. Plain <a> links to Identity/Account/... would otherwise be intercepted by Blazor WASM's client-side Router and rendered as <NotFound>. Also removed the SignOutSessionStateManager dependency (deprecated in .NET 7, removed in .NET 8).
  • Client/Shared/RedirectToLogin.razor — points unauthorized users at Identity/Account/Login?ReturnUrl=... (with forceLoad: true). The ReturnUrl is new Uri(Navigation.Uri).PathAndQuery (relative path) so Login.cshtml.cs's LocalRedirect(returnUrl) / IsLocalUrl() accepts it after a successful login.
  • Client/Pages/Reports.razor — dropped the IAccessTokenProviderAccessor and Microsoft.AspNetCore.Components.WebAssembly.Authentication / .Internal dependencies. Cookie auth means the browser sends the auth cookie automatically with every same-origin request, so easyquery.blazor.startAdhocReporting(...) is invoked without a bearer token. The role-gated UI still works via the new HostAuthenticationStateProvider.

These changes keep the auth surface compatible enough that the project builds and the cookie-based login/register/logout flow continues to work via the existing Identity UI Razor Pages.

Build commands & results

Run from AspNetCore/Blazor/AdHocReporting.BlazorWasm/ with .NET SDK 8.0.420 (Linux x64):

dotnet restore Server/EqDemo.BlazorWasm.AdhocReporting.Server.csproj
dotnet build   Shared/EqDemo.BlazorWasm.AdhocReporting.Shared.csproj -c Release
dotnet build   Client/EqDemo.BlazorWasm.AdhocReporting.Client.csproj -c Release
dotnet build   Server/EqDemo.BlazorWasm.AdhocReporting.Server.csproj -c Release

Result:

  • Shared: Build succeeded. 0 Warning(s) 0 Error(s)
  • Client: Build succeeded. 0 Warning(s) 0 Error(s)
  • Server: Build succeeded. 2 Warning(s) 0 Error(s)

The two Server warnings are both NU1901 advisories on NuGet.Protocol 5.11.5, which is out of the upgrade scope (the prompt explicitly says "Do NOT bump unrelated dependencies just because newer versions exist"). They are unrelated to the .NET 8 upgrade itself.

CI

  • Devin Review — flagged 5 functional issues during review; all addressed in follow-up commits and replied to in-thread (missing AuthenticationStateProvider, Reports.razor IAccessTokenProviderAccessor usage, anti-forgery POST issue, Blazor router intercepting Identity/Account/* links, missing role claims in cookie).
  • security/snyk (Cognition-default)failed (3 tests have failed). The Snyk dashboard requires authenticated access I do not have. The same Snyk failure mode appears on the parallel net8 upgrade PRs in this repo (e.g. PR chore(net8): upgrade AspNetCore/Vue2/AdvancedSearch to .NET 8 #35 noted the identical issue), so this looks like a pre-existing repo-wide Snyk configuration issue, not a regression introduced by this PR. This PR actually removes several historically vulnerable packages (System.Drawing.Common 4.7.2, System.Net.Http 4.3.4, System.Text.RegularExpressions 4.3.1, System.Data.SqlClient 4.8.6) and bumps Microsoft.Data.SqlClient to a fully patched 5.2.2.
  • license/snyk (Cognition-default)failed (3 tests have failed). Same situation as security/snyk above; flagging for human review since dashboard access is needed to see the specifics.

Review & Testing Checklist for Human

This is a yellow-risk change (.NET 8 framework bump plus replacing the entire IdentityServer auth stack with cookie auth). Please verify:

  • dotnet build -c Release succeeds against all three projects on a clean checkout (with the .NET 8 SDK installed).
  • Register a new user via the Register link → log in → verify <AuthorizeView> flips to Authorized (the LoginDisplay shows the username and a "Log out" link), and /reports no longer redirects to login.
  • Verify the role-gated reports UI: register a user with a role of eq-manager (or seed one) and confirm the "New report / Save as / Remove report" dropdown items become enabled. Without the explicit role-aware claims factory registration in Server/Program.cs, this would silently always be disabled — please confirm it actually works end-to-end.
  • Verify the EasyQuery MapEasyQuery(...) endpoints (/api/adhoc-reporting/...) are reachable from the Blazor client and that the cookie-based User principal is correctly populated server-side.
  • Open the Snyk dashboard via the failed PR check links and confirm whether the flagged advisories are pre-existing (i.e. also flagged on master) or actually introduced by this PR.

Test plan recommendation:

  1. Restore + build all three projects from a clean clone with .NET 8 SDK.
  2. dotnet run --project AspNetCore/Blazor/AdHocReporting.BlazorWasm/Server to launch the demo against the bundled SQLite database.
  3. Register a new user; if Server/Areas/Identity/Pages/Account/Register.cshtml.cs still adds the user to the eq-manager role on first registration (it did pre-upgrade), verify the manager-only menu items are enabled on /reports.
  4. Sign out → verify the auth cookie is cleared and /reports redirects to login again.

Notes

  • No changes were made outside AspNetCore/Blazor/AdHocReporting.BlazorWasm/.
  • No global.json / Directory.Build.props were added at the repo root (out of scope; owned by Session 7).
  • No .sln files were edited.
  • The IdentityServer config block in Server/appsettings.json was removed because nothing reads it anymore.
  • Microsoft.IdentityModel.JsonWebTokens and System.IdentityModel.Tokens.Jwt are pinned to 7.6.0 per the prompt's "latest stable 7.x" guidance.

Link to Devin session: https://app.devin.ai/sessions/4209388e71e3468985471646a9440b95
Requested by: @tobydrinkall


Devin Review

Status Commit
⚪ Not started

Run Devin Review

💡 Connect your GitHub account to enable automatic code reviews.

Open in Devin Review (Staging)
Open in Devin Review

- Bump TargetFramework to net8.0 on all three projects
- Replace Microsoft.AspNetCore.ApiAuthorization.IdentityServer (removed in
  .NET 8) with ASP.NET Core Identity cookie auth (AddDefaultIdentity +
  Identity UI Razor Pages)
- Drop ApiAuthorizationDbContext for IdentityDbContext<ApplicationUser>
- Remove OidcConfigurationController (depended on removed package)
- Remove RemoteAuthenticatorView/SignOutSessionStateManager usage on the
  client (obsolete in .NET 8); redirect /authentication/{action} to the
  Identity Razor Pages
- Bump Microsoft.EntityFrameworkCore.* to 8.0.11
- Bump Microsoft.AspNetCore.Components.WebAssembly.* to 8.0.11
- Bump Microsoft.AspNetCore.Identity.* to 8.0.11
- Bump Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore to 8.0.11
- Add Microsoft.AspNetCore.Authentication.JwtBearer 8.0.11
- Bump Microsoft.Data.SqlClient to 5.2.2
- Bump Microsoft.IdentityModel.JsonWebTokens / System.IdentityModel.Tokens.Jwt to 7.6.0
- Remove Microsoft.VisualStudio.Web.CodeGeneration.Design,
  System.Data.SqlClient, System.Net.Http, System.Text.RegularExpressions,
  System.Drawing.Common (no longer needed; provided by .NET 8 BCL)

Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
@devin-ai-integration
Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

… decouple Reports.razor from IAccessTokenProviderAccessor

- Add EqDemo.Client.HostAuthenticationStateProvider that resolves auth
  state by calling a small /_auth/me endpoint on the server (which is
  authenticated via the ASP.NET Core Identity cookie). This restores the
  AuthenticationStateProvider DI registration that was previously provided
  by AddApiAuthorization() so <CascadingAuthenticationState> resolves at
  runtime.
- Register HostAuthenticationStateProvider in Client/Program.cs alongside
  AddAuthorizationCore().
- Add /_auth/me minimal API endpoint in Server/Program.cs that returns
  the current cookie-authenticated user's claims.
- Update Client/Pages/Reports.razor to drop the OIDC-only
  IAccessTokenProviderAccessor dependency. With cookie auth the auth
  cookie is sent automatically with same-origin requests, so no bearer
  token is needed for the EasyQuery JS bootstrap.

Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
devin-ai-integration[bot]

This comment was marked as resolved.

…ink to avoid anti-forgery 400

The Blazor WASM client cannot inject Razor Pages anti-forgery tokens, so a
direct POST to Identity/Account/Logout fails with 400 Bad Request. Use a
plain <a> link to the server-rendered logout page instead — the page's own
form has the anti-forgery token and signs the user out on click.

Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
devin-ai-integration[bot]

This comment was marked as resolved.

…} and pass relative ReturnUrl

- LoginDisplay.razor: revert href values to authentication/{login|logout|
  register|profile}. Plain absolute Identity/Account/* hrefs are
  intercepted by Blazor's client-side Router and rendered as <NotFound>;
  routing through Authentication.razor (which forceLoad-redirects to the
  Identity Razor Pages) gives a real full-page navigation.
- RedirectToLogin.razor: pass PathAndQuery (relative) as ReturnUrl
  instead of the full absolute URI, otherwise Login.cshtml's
  LocalRedirect/IsLocalUrl rejects the value and throws after a
  successful login.

Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
devin-ai-integration[bot]

This comment was marked as resolved.

AddDefaultIdentity<TUser>() registers UserClaimsPrincipalFactory<TUser>
via TryAddScoped, so the role-aware factory that AddRoles<IdentityRole>()
tries to register is silently dropped. Explicitly register
UserClaimsPrincipalFactory<ApplicationUser, IdentityRole> after
AddEntityFrameworkStores so User.IsInRole("eq-manager") is honoured both
in /_auth/me and in EasyQuery's auth provider.

Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
@devin-ai-integration
Copy link
Copy Markdown
Author

Test report — cookie-auth + role projection verified end-to-end

Ran the upgraded Server project on .NET 8 SDK 8.0.420 locally (http://localhost:5257), exercised the full auth + role flow through the browser as the seeded demo@korzh.com / demo user. Recording · Devin session

Escalation (pre-existing, not a regression from this PR)

Server/Services/DefaultReportGenerator.cs:28 uses a literal Windows backslash: Path.Combine(env.ContentRootPath, $"App_Data\\Seed"). On Linux, Path.Combine treats \\ as a regular filename character, so Directory.GetFiles(_dataPath, "*.json") throws DirectoryNotFoundException: …/Server/App_Data\Seed. This crashes self-registration's post-SignInAsync report-generation step. I verified via git show master:.../DefaultReportGenerator.cs that the bug exists identically on master — out of scope for this PR. Worth a small follow-up to replace the literal "App_Data\\Seed" with Path.Combine("App_Data", "Seed").

Results

Test Result
Anonymous /reports redirects to /Identity/Account/Login with a relative ReturnUrl=%2Freports
demo@korzh.com / demo log in → LocalRedirect to /reports; <LoginDisplay> flips to Hello, demo@korzh.com! + Log out
Role-gated cog menu in Reports.razor renders the manager branch (enabled #NewReportButton/#SaveReportButton/#RemoveReportButton, no disabled class, no consumer-role tooltip)
Logout clears the auth cookie
Post-logout /reports redirects to Login again
Self-registration end-to-end ⚠️ blocked by the pre-existing Linux path bug above
Load-bearing assertion: role claims actually land in the cookie

Reports.razor:113 does IsManager = authState.User.IsInRole("eq-manager") and switches between two visually-distinct DOM branches. With the UserClaimsPrincipalFactory<ApplicationUser, IdentityRole> registration in Server/Program.cs:34-35 (commit 9602509) broken, the user could still log in but IsInRole would return false and the disabled branch would render — same login, visibly different DOM.

Captured DOM of /reports after login:

<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
  <a class="dropdown-item" id="NewReportButton"  href="javascript:void(0)">New report</a>
  <a class="dropdown-item" id="SaveReportButton" href="javascript:void(0)">Save as...</a>
  <a class="dropdown-item" id="RemoveReportButton" href="javascript:void(0)">Remove report</a>
</div>
  • No disabled class → manager branch
  • IDs only present in the manager branch (Reports.razor:32-35); the non-manager branch's items have no IDs (Reports.razor:43-46)
  • No "consumer role" tooltip

DB confirms demo@korzh.com is in the eq-manager role:

$ sqlite3 Server/eqdemo-sqlite.db "SELECT u.Email, r.Name FROM AspNetUsers u
    LEFT JOIN AspNetUserRoles ur ON u.Id=ur.UserId
    LEFT JOIN AspNetRoles r ON ur.RoleId=r.Id;"
demo@korzh.com|eq-manager
Screenshots
Login (anonymous /reports → Identity Login w/ relative ReturnUrl) Reports authed as demo@korzh.com (LoginDisplay flipped; manager DOM under hidden EasyQuery overlay) Logout success

The center screenshot's full-page "No license key for EasyQuery" overlay is pre-existing demo behaviour — Reports.razor:17 ships style="visibility: hidden" on the ReportsContainer and only the EasyQuery JS bootstrap toggles it; without a license key that JS never runs. The role-gated DOM is rendered correctly inside the hidden container (DOM snippet above).

Happy to do a follow-up for the DefaultReportGenerator path fix on a separate PR if helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant