Skip to content

AuthorizeView should expose the results of the AuthorizeAsync call as a context variable. #43959

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 task done
michac opened this issue Sep 13, 2022 · 8 comments
Open
1 task done
Labels
area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one good first issue Good for newcomers. help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team
Milestone

Comments

@michac
Copy link

michac commented Sep 13, 2022

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

I am using asp.net Policy to enforce licensing restrictions. When a user is not authorized to view or use a component I want to be able to tell them why they are not authorized. This is supported via Policy, where you can include a reason why access is denied that then shows up in the AuthorizationResult as a AuthorizationFailureReason. AuthorizeViewCore, however, throws away all this metadata and boils the result down to a true/false for which view to show.

Describe the solution you'd like

I wrote a custom AuthorizeViewCore that wraps the AuthenticationState and the AuthorizationResult in a new type, and makes that available as a context variable. Now my NotAuthorized view can show a tooltip or other message indicating why it's not authorized (ex. "this is disabled because you are not licensed for this feature").

Additional context

No response

@mkArtakMSFT mkArtakMSFT added the area-blazor Includes: Blazor, Razor Components label Sep 13, 2022
@javiercn
Copy link
Member

@michac thanks for contacting us.

Your ask sounds reasonable, but I'll let other people on the team chime in.

@mkArtakMSFT mkArtakMSFT added the enhancement This issue represents an ask for new feature or an enhancement to an existing one label Sep 14, 2022
@mkArtakMSFT mkArtakMSFT added this to the Backlog milestone Sep 14, 2022
@ghost
Copy link

ghost commented Sep 14, 2022

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@mkArtakMSFT mkArtakMSFT added help wanted Up for grabs. We would accept a PR to help resolve this issue good first issue Good for newcomers. labels Sep 14, 2022
@mkArtakMSFT
Copy link
Member

@michac feel free to send us a PR with the fix. We'd be happy to consider it.

@IDisposable
Copy link

IDisposable commented Mar 14, 2023

Looks like the simplest change that could work would be in AuthorizeViewCore.cs to replace the private bool? isAuthorized; with private AuthorizationResult? currentAuthorizationResult; .

Then in BuildRenderTree:

  • We do the null check against currentAuthorizationResult for the Authorizing case.
  • If not null we check currentAuthorizationResult.Succeeded for the Authorized case.
  • If not null and not Succeeded (the else clause), we would pass down the currentAuthorizationResult along with the state like builder.AddContent(0, NotAuthorized?.Invoke(currentAuthenticationState!, currentAuthorizationResult!));.
    • This would add a new argument to the Invoke to the NotAuthorized RenderFragment... that would break existing usages right @mkArtakMSFT?

Finally, around line 99, we replace the Task<bool> IsAuthorizedAsync( with Task<AuthorizationResult> AuthorizeAsync( and change the last two lines (at 112 and 113) to simply return the whole value that AuthorizationService.AuthorizeAsync( was previously returning instead of returning the dotted-into Succeeded value.

Obviously we would need to adjust the tests accordingly, but this seems like a relatively safe and simple change if the addition of the argument to the NotAuthorized fragment won't break things... I simply don't know that...

This path doesn't change any public exposure except that if we would also want to expose the authorization result for others via a new property public AuthorizationResult AuthorizationResult { get { return currentAuthorizationResult }; }, but that would only be required if we didn't have the value getting passed to the Invoke.

@IDisposable
Copy link

IDisposable commented Mar 14, 2023

A trial implementation of this reveals that Invoke can only take one argument, so I guess we do need to introduce a wrapping object or modify the declaration of the NotAuthorized fragment. This means that we would have some breaking changes to the people providing the RenderFragment for NotAuthorized . What's the desired approach for this? Do we create a different template that expects a State+Result object?

@IDisposable
Copy link

The changing of the NotAuthorized argument would break so much, it looks like the only reasonable path is to just expose the AuthorizationResult directly on AuthorizeViewCore.cs which likely isn't actually very useful.

@IDisposable
Copy link

IDisposable commented Mar 15, 2023

Can the AuthorizedViewCore have more than one [CascadingParameter]?

@mkArtakMSFT mkArtakMSFT added help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team and removed help wanted Up for grabs. We would accept a PR to help resolve this issue labels Oct 28, 2023
@mkArtakMSFT mkArtakMSFT modified the milestones: Backlog, BlazorPlanning Nov 5, 2023
@mkArtakMSFT mkArtakMSFT modified the milestones: Planning: WebUI, Backlog Nov 19, 2023
@dotnet dotnet deleted a comment Nov 19, 2023
@willdean
Copy link

(Just as an aside, this issue is a more general version of #44162, which was probably rather too specific.)

I've had a go at adding this feature to the framework source, because A) I would like to use it and B) it saddens me that so much good information in AuthorizationResult gets discarded by Blazor, and C) the existing components can't be extended or have their innards reused.

As @IDisposable discovered above, the challenge in implementing this is not really preserving the AuthorizationResult, it's passing it onto the NotAuthorized child, who already takes a @context value of the type AuthenticationState

I tried/considered a few things:

Technique Pros Cons
Wrap an AuthorizationResult cascading parameter around the NotAuthorized child. Probably doesn't break anything unless there's an existing cascading parameter that clashes. Might break stuff if there is a clashing cascading parameter? Needs the NotAuthorized child to be a component if they want to pick up the AuthorizationResult. Utterly undiscoverable. Changes the shape of the output so breaks all the tests, which probably implies extra risk
Change the context type of NotAuthorized to be some completely new type that includes AuthorizationResult AND AuthenticationState Probably how it should have been in the first place, would be perfectly discoverable and intuitive. Catastrophic breakage of everything which already uses @context. I didn't bother.
Add an additional child alongside NotAuthorized - something like NotAuthorizedButWithLotsMoreInformation and pass the completely new type from the previous row as the context for this child. Complete separation from existing code, quite discoverable. Overhead (lots of duplication in the xxView classes). Complications: e.g. Are both the children rendered if they're both present?
Derive a class from AuthenticationState that adds in an AuthorizationResult and pass on object of that type as the @context of NotAuthorized. People who want the additional information can down-cast @context to the new type. Doesn't affect existing apps. Doesn't break any existing test. Can be used directly by child content of NotAuthorized, doesn't need a component. Not discoverable. Needs ugly cast. Creates new public type. I found it incredibly hard to think of a name for this class, which is usually a bad sign... Is it a horrible hack?

Anyway, I implemented the last one. It didn't break any tests and was easy to add one new test for. But I don't know if this sort of approach (passing an object by its base class even though it's always a derived type) is considered to be a disgusting hack unworthy of a PR, and for that I would need some MS input.

I hesitate to tag @mkArtakMSFT but you did mark it "help candidate" - can you connect us with someone who could opine on the most acceptable approach here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one good first issue Good for newcomers. help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team
Projects
None yet
Development

No branches or pull requests

7 participants
@IDisposable @willdean @michac @javiercn @mkArtakMSFT and others