Might want to check out BlazorServerIdentityInterop. (https://github.com/bdnts/BlazorServerIdentityInterop) Same goals, out of the box Blazor Server with Identity scaffolded but with Razor pages. It uses JSRuntime to get the XSRF token, dynamically create a form, and then submit the form to classic Login or Logout, where SignInManager
actually works. it is based on work from Shaun Walker on the Oqtane (https://oqtane.org) project.
Conclusion: It can't be done with the template code during project creation.
- The files generated by scaffolding Identity are not Blazor pages.
- Login cannot be converted to Login.Razor, because SignInManager will always throw an exception regarding "The response headers cannot be modified because the response has already started."
- This error is caused because of the differences in HTTP calls and SignalR that Blazor is built upon. You can't put a stamp and postmark on a telephone call. You have to use CallerId.
- I cannot find any way around this exception without significant rework
- Build an API service to perform Login, and call it.
- Follow SignalR documentation to use Bearer Tokens via an Identity Server.
If you want to follow my notes below, possibly find a mistake I made, please, be my guess. But I'm pretty confident that SignInManager is incompatible with Blazor, for a lot of well documented reasons. I would love to be proven wrong.
This project is concluded.
Notebook
https://docs.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1&tabs=visual-studio
- Create blank blazor application
- Commit to Git repository public
- Tag Base created
- BlazorIdentity --> Add --> New Scaffold Item
- Identity --> Add
- Wait for it
- New Layout Page – No change
- Override all files –check
- Data Context Class
- '+
- BlazorIdentityContext
- Add
- User Class
- '+
- BlazorIdentityUser
- Add
- Add
- Wait for it
- ScaffoldingReadMe.txt
- ScaffoldingReadMe.txt
- Startup.cs
- App.UseStaticFiles() already exists
- Add app.UseAuthentication() after static files
- MVC
- Going to re-write all the pages as Blazor/Razor pages, so no need
- Database
- Default is to use sql express. I use SQL Server! (hrrumph!)
- Appsettings.json
- "IdentityContextConnection": "Server=;Database=BlazorIdentity;Trusted_Connection=True;MultipleActiveResultSets=true"
- Will setup user secrets in a little bit
- Add-migration CreateIdentitySchema
- Build failed
- RegisterConfirmation.cshtml.cs UserManager<> could not be found
- Add using Microsoft.AspNetCore.Identity;
- Successful build
- Repeat add-migration CreateIdentitySchema
- Success
- Update-database
- Success
- Database Created
- Commit Changes
- Except appsettings.json
- Build failed
- Startup.cs
- User Secrets
- Right click BlazorIdentity --> Manage User Secrets
- Transfer ConnectionStrings {} to secrets.json
- Remove extra comma if necessary
- Compile
- Debug
- Wait for it – Hello World
- Commit Changes
- Everything now
- Create tag
- Optional – Enable Beyond Compare for source code diffing
- In Solution --> BlazorIdentity -->.git --> config
[diff]
tool = bc4
[difftool "bc4"]
prompt = false
[difftool "bc4"]
cmd = \"C:\\Program Files\\Beyond Compare 4\\Bcomp.exe\" \"$LOCAL\" \"$REMOTE\"
keepBackup = false
[merge]
tool = bc4
[mergetool "bc4"]
prompt = false
[mergetool "bc4"]
cmd = \"C:\\Program Files\\Beyond Compare 4\\Bcomp.exe\" \"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"
keepBackup = false
trustExitCode = true
No restart necessary
- Reference pages
- https://docs.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1&tabs=visual-studio
- https://docs.microsoft.com/en-us/aspnet/core/security/?view=aspnetcore-3.1
- https://docs.microsoft.com/en-us/aspnet/core/security/blazor/server?view=aspnetcore-3.1
- https://docs.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.1&tabs=visual-studio
- https://docs.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-3.1&tabs=visual-studio
- https://chrissainty.com/securing-your-blazor-apps-introduction-to-authentication-with-blazor/
- https://chrissainty.com/securing-your-blazor-apps-authentication-with-clientside-blazor-using-webapi-aspnet-core-identity/
- https://github.com/stavroskasidis/BlazorWithIdentity
- https://docs.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1&tabs=visual-studio#authentication
- https://github.com/AdrienTorris/awesome-blazor#authentication
- BlazorApp9
- Created with Identity to compare against BlazorIdentity
- Found many minor and a few major differences. Hard to tell which is correct, but lean towards BlazorApp9
- RevalidatingIdentityauthenticationStateProvider.cs
- Pulled across file, but then needed to change namespace
- Startup.cs
- App.UseDatabseErrorPage not found
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.1.1" />
- Must have missed a step in the scaffolding
Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
- App.UseDatabseErrorPage not found
- Follow https://docs.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-3.1&tabs=visual-studio
- Startup.cs
- Many items noted for Startup.cs are actually in IdentityHostingStartup.cs
- AddDbContext
- AddDefaultIdentity
- AddEntityFrameworkStores
- Many items noted for Startup.cs are actually in IdentityHostingStartup.cs
- Startup.cs
- Compile and run
- Success
- Register
- [email protected]
- Password1234#
- Acccount registered
- No email confirmation yet
- Account confirmed
- Login
- Login successful
- Account confirmed in database
- Resuming https://docs.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1&tabs=visual-studio#authentication
- Index.razor
- Copied over the Index verbatim
- Successful, but dull
private string message { get; set; } = "None";
private async Task LogUsername()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
message = $"{user.Identity.Name} is authenticated.";
}
else
{
message = "The user is NOT authenticated.";
}
}
- App.Razor
- BlazorApp wrap all components in
<CascadingAuthorizationState>
Documentation only wraps Layout. - I'll go with code over documentation
- BlazorApp wrap all components in
- AuthorizeView Component - Index.razor
- Used
<AuthorizeView>
and<Authorized>
tags to control content - Added Roles of admin, member and family to control output
- Doesn't appear there is a RolesAdmin page. Have to make one. Will make it a blazor page.
- Created folder Pages/Identity/Mange
- Create Roles.razor in folder
- Used
- Seed Database
- Quick method
- Manually add records to AspNetRoles, AspNetUsers, and join table AspNetUserRoles
- Slow method, through OnModelCreate
- Found https://docs.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-3.1&tabs=visual-studio
- https://docs.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-3.1
- https://stackoverflow.com/questions/59229067/how-can-i-seed-users-and-roles-in-my-asp-net-core-3-1-application
- Modify BlazorIdentityContext
- Insert 3 roles for admin, member and family.
- IdentityHostingStartup.c
- Under
services.AddDefaultIdentity
, add.AddRoles<IdentityRole>();
- Under
- Database
- CAUTION: Migration and update-database might not work if connection string is in secrets.json. Added it back to appsettings.json, and things started working. Will let someone else dig into this.
- Drop the database
- Remove the migration
- Create a new migration
- Update the database (create)
- Check database that roles are present
- Run application and create user
- Manually populate AspNetUserRoles
- Quick method
- Logout error – anti forgery token exception
[IgnoreAntiForgeyToken]
added to LogoutMode
- Admin page
- Michael Washington https://blazorhelpwebsite.com/Blog/tabid/61/EntryId/4354/A-Simple-Blazor-User-and-Role-Manager.aspx
- Change
<IdentityUser>
to<BlazorIdentityUser>
- Change "Administration" to "admin"
- Roles
- Changed Admin page to use Roles based authorization
- Removed the direct if statement check, adding the admin role
<AuthorizeView Policy="admin">
- Policy and Claims based Authorization is great, but later.
<Authorized>
is for Webassembly, not today[Authorize]
is for pages only. Will convert Administration page- Successful. Simple message from the Router.
- Changed App.razor to give innocuous message. You don't want to confirm or deny page exists.
- BONUS: Added
<AuthorizedView Roles="admin">
around NavMenu Administration element, and it is hidden unless authorized. Way cool. - Cleaned up Index a bit.
- Blazor Login screen
- LoginB
- Copied Administration over to get the full monty
- The key should be having the UserManager DI
- Let's start with a page, and work towards a component
- Logging
- There is a blazor extension for logging (That should be a Microsoft deliverable, not third party)
- https://github.com/BlazorExtensions/Logging
- Will come back to this. Commenting out for now.
- Dropped all the HTML from LoginB, put
SignInManager.PasswordSignInAsync()
intoOnInitializedAsync()
, to determine if this is even possible. - ERROR: "The response headers cannot be modified because the response has already started.", or some such bilge.
- Basically, MS Identity is dependent on HTTP as transport, so, Blazor (thankfully) is using SignalR websockets.
SignInManager.PasswordSignInAsync()
won't work. MS Identity is the problem here. Maybe I should step back into 1996 and pull out the old code to fix this!
- Searching the Web, everyone has this problem, and no news in sight from MS. Unbelievable.
- Found really good explanation of error https://danpatrascu.com/manipulating-response-headers-in-asp-net-core/
- This is a MAJOR architectural screw up in MS Identity.
- LoginB is dead
- Blazor Authentication and Authorization by Chris Sainty
- Part 1 - https://chrissainty.com/securing-your-blazor-apps-introduction-to-authentication-with-blazor/
- Nice intro to Blazor Server A A, but the pages are all Asp.Net, not Blazor pages. How to make Authentication from Blazor pages?
- Part 2 - https://chrissainty.com/securing-your-blazor-apps-authentication-with-clientside-blazor-using-webapi-aspnet-core-identity/
- Creates a WebAssembly app that calls Login APIs on server. Cool, but not GA.
- Part 1 - https://chrissainty.com/securing-your-blazor-apps-introduction-to-authentication-with-blazor/
- BlazorWithIdentity - https://github.com/stavroskasidis/BlazorWithIdentity
- This could be something. It appears they created a custom authenticator that uses MS Identity below.
- It uses the same api approach in WebAssembly as Chris Sainty.
- Would hate to go to all this work building API if there is a simple solution LoginB error.
- Borrowed forms and combined logic of LoginB:
- Getting same exception: "The response headers cannot be modified because the response has already started."
- So close, and then to be defeated like this. ARRRRRGGGGHHH!
- LoginB