Code Monkey home page Code Monkey logo

blazorserveridentity2's Introduction

BlazorServerIdentity2

A Blazor Server Project with Identity, and Blazor components for Identity. Version 2.0 using RevalidatingServerAuthenticationStateProvider (RSASP)

This is another effort to find a KISS approach to building a Blazor Server App with all Blazor components.

Disclaimer

I'm just a programmer (maybe a little more than just). I've got my own projects, headaches, and deadlines (one generates the other). In the course of my work, I've come up against a lot of issues regarding Blazor and Identity.
No complaints, this is normal teething problems with a new product. It takes time for guidance to solidify around concensus approaches and from there for documentation to catch up. In order to solve my own problems, I did a LOT of experimentation (and not with pharmaceuticals). I was asked if I could share the results of my experiments, and this and other projects are the output. This is by no means the definitive answer, best practice, concensus guidance, etc.
This is what worked for me, in my use cases, with my priorities. I hope it can help someone else.

Known Issues

New Approach

RevalidatingIdentityAuthenticationStateProvider (RIASP [rye asp])

Steps

  • Create Blazor Server App
  • Add Identity Local Users
  • Compile

Base 00.00.00 Created

  • There are no Razor pages created by this method. The Login, Register, and other pages are within Microsoft.AspNetCore.Identity. Those files are only created by Scaffolding Identity
  • A SQL Express database is created and awaits Entity Framework migrations
    • In Package Manager
    > add-migration M1
    > update-database  
    
  • Run the application F5
  • Register a new user
      Email=test01@email
      Password=********
    
  • Send Confirmation email
    • Email is confirmed
  • Login to application
  • Successful, should see user name displayed

RevalidatingIdentityAuthenticationStateProvider

  • This Authentication State Provider was built by the template, and is the key to sucessful Local Identity Authentication and Authorization in Blazor.
    • RIASP inherits from RevalidatingServerAuthenticationStateProvider (RSASP)
      • RSASP inherits from ServerAuthenticationStateProvider (SASP)
        • SASP inherits from AuthenticationStateProvider and IHostEnvironmentAuthenticationStateProvider
    • This is important because as it says in the SASP header:
      An Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider intended for use in **server-side Blazor**. [Emphasis added]
      • Not only can it support on-prem AD A&A, but host based A&A. This is what I was looking for.
    • RIASP comes with 2 methods, ValidateAuthenticationStateAsync() and ValidateSecurityStampAsync() These methods do as their name implies, revalidate the authentication state on a periodic basis. That way if a user's credentials change, every 30 minutes so they get refreshed. Therein lies the clue: If a User's credentials can be refreshed (or revoked), they they must be able to be established as well!
    • I found one or two sources with concrete examples and that was it. But those provided just enough example of how to signin a user. Well, they worked, and worked well. I found that I could replicate the functionality in SigninManager() and UserManager() within RIASP, sometimes using UserManager() but without the downsides of other methods.

Downsides

Razor Pages

  • The razor pages generated by Scaffolding Identity work, they work right. Do everything they are supposed to. But they don't behave like Blazor Components, because they aren't.
    The UI/UX is not Blazor. And it is noticable. Blazor components can be incorporated into Razor pages, but vice versa is not so easy.
  • Converting the Razor Identity pages is to look like the default Blazor components is possible, no easy feat.

Interop

One suggested solution is to POST to the Razor Pages methods from Blazor UI components.
Theoretically sound but soon a lot of issues ensue.

  • Cross Site Scripting (XSRF). Can't POST unless you set to the [IgnoreAntiForgeryAttribute]. If your use case can accept this, ok. But still a lot of work to wire up the Blazor Components to POST to the Razor methods.
  • Interop. On suggestion was to save of the XSRF token in the start up Razor page, then access it from Blazor, and attach it to the form before POSTing. Well, it works. But as one asute observer pointed out That's a bit of kludge Lol. And he was right. A lot of work for very little gain.

API

Another suggestion was build an API controller and make http calls to the API. API methods can perform the SignUp, SignIn, SignOut (SUSISO) duties.
And that is the way you want to do it for Production, and for WASM. And you want to use tokens. And that is quite a bit of work.

  • You also have to be careful that the token or cookie goes into the Browser, not just back to your Blazor component. Easier said than done.

Third Party

A solid solution, for Production. Kinda heavy weight for dev.

Azure AD

Wow! Works right out of the box, exactly as the documentation says. Great solution IF your wedsite users are on AD.

Azure AD B2C

If you are going onto the Web, this is an impressive solution. The scale, the security, the reliability, what's not to like. But we are back to the Razor Pages issue, the UI is not Blazor. If you want to diverge from the default path (a UserName instead of Email as identifier) the UX requires full customization. When ready to go Production, this is probably the way I'll go, but I gotta finish functionality before I start customizing the whole B2C system. I'm guessing a month or two, and I'm still left with a non-Blazor UI. frown

RIASP

There are no Razor pages to call. There is no XSRF issue. No APIs. No third parties. There is no SignInManager throwing exceptions.

Exceptions are thrown because there is no HttpContext in Blazor: 
And the reason why guidance is not to use HttpContextAccessor.
There are Lots of stuff on StackOveflow and elsewhere about this.
("Why is HttpContext null when I deploy to Azure?")
I won't go into huge detail, but when you are testing on your local box,
the browser is connected to the server across the loopback interface.
So there is an HttpContext.  But when you leave your box (I test with 
another machine or mobile device over WiFI) there is no local interface 
and no HttpContext. And there never will be.  This is why SigninManager
can never do a SignIn--unless someone changes the programming.

With RIASP, none of the other issues exist, except, there are no cookies in the browser. So when the user is authenticated, it is only at the Server. Refresh the browser screen, and the authentication is lost. I know there are solutions to this, I've seen Mike Washington's ADefServer use cookie authentication (but with HttpContextAccessor. Hmmn), so I know it can be done, just haven't had the time to investigate. In the meantime, this is not a big issue. I don't refresh the screen. But otherwise A&A works as documented. Almost all of the work is in RIASP, and then building SUSISO pages. And RIASP is templated, so it is easy to move from project to project, with different IdentityUser classes.
I've probably spent more time writing this Readme than building this project!

If someone can help with the browser cookie, I think this can be a real sweet out of the box solution.

Base 01.01.01 Bug fix

  • RevalidatingIdentityAuthenticationStateProvider.razor
    • Restored generation of usermanager

Base 01.01.00 SignUp

Adding SignUp to system.

Encountered the dreaded Entity cannot be tracked error.

System.InvalidOperationException: The instance of entity type 'IdentityUser' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.`
I was using a pattern of `FindById()` and then `ConfirmEmailAsync()

Because the Find is by Id, the Key, Entity Framework locks the record in one DBContext, and then I can't write to it with a different context (ConfirmEmailAsync()). While there might be a elegant way to deal with this, I haven't found it. I've gone through all the usual answers found on SO, but none worked for me. When using a common UserManager, this issue doesn't occur because it is the same DBContext. With RIASP, each call spins up a new UserManager, so FindById() and ConfirmEmailAsync() use different instances and different DBContexts. Of the several possible solutions, the simplest is to combine the 2 operations into one new operation. So I created ConfirmEmailPlusFindByIdAsync() that performs both with one UserManager.
This has no analog in UserManager, which I've tried to keep RIASP aligned to. But alas, it is not a perfect world.

  • RevalidatingIdentityAuthenticationStateProvider.razor

    • Added _ to logger
    • Added ConfirmEmailAsync()
    • Added ConfirmEmailPlusFindFirstAsync()
    • Added CreateAsync()
    • Added FindByIdAsynd()
    • Added GetUrl()
  • SignUp.razor SignUp.razor.cs SignUpEmailConfirmed.razor

    • All 3 added to support SignUp flow.
  • LoginDisplay.razor

    • Modified to display a SignUp item.
  • BlazorServerSIdentity2.csproj

  • Added package Microsoft.AspNetCore.Components.DataAnnotations.Validation" Version="3.2.0-rc1.20223.4 to support the [Compare] operator.

  • site.css

    • Added some new classes to help align SignUp and SignIn SignIn.razor
    • Beautified and better aligned things IdentityExtensions Bonus
  • This is a extender class for Identity classes. For now, just Navigationmanager. Chris Sainty had posted an extension on how to attributes off the URL. I used it and incorporated it. This isn't absolutely necessary, but he does good work, I borrowed/stole it, but give him attribution. Enjoy.

Base 01.00.00

  • Required changes to make this solution work.
    • RevalidatingIdentityAuthenticationStateProvider.razor
      • Major additions of all the supporting methods Startup.cs
      • Major changes for making RIASP accessible and usable. SignIn.razor SignOut.razor
      • New Razor components for SUSISO
    • Migration generated files*
  • Optional changes to improve usefulness LoginDisplay.razor
    • Minor change adding NavLinks to display SignIn and SignOut
  • Index.razor
    • Minor addition of a little fluff demonstrating Imports.razor
    • Minor addition adding Components to imports list site.css
    • Minor addition to help things line up.
  • ServerSideValidator.cs Bonus
    • Bonus method for doing validation of EditForms

blazorserveridentity2's People

Contributors

bdnts avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.