Skip to content

Blazor with multiple auth middleware #14916

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

Closed
aL3891 opened this issue Oct 11, 2019 · 6 comments
Closed

Blazor with multiple auth middleware #14916

aL3891 opened this issue Oct 11, 2019 · 6 comments
Labels
area-blazor Includes: Blazor, Razor Components

Comments

@aL3891
Copy link

aL3891 commented Oct 11, 2019

Describe the bug

When using multiple auth middleware (AzureAD and AzureAdBearer in my case) blazor seems unable to find the logged in user. The user is logged in though, its presented with the azure login ui and visiting a non-blazor page does correctly show the logged in user.

To Reproduce

Steps to reproduce the behavior:

  1. Create a new blazor server app in visual studio, with work/School auth.

  2. In Startup.cs, replace the AddAuthentication and AddControllersWithViews calls with the following

     services.AddAuthentication(options => { options.DefaultChallengeScheme = AzureADDefaults.AuthenticationScheme; })
         .AddAzureAD(options => Configuration.Bind("AzureAd", options))
         .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
     
     services.AddControllersWithViews(options =>
     {
         var policy = new AuthorizationPolicyBuilder(AzureADDefaults.BearerAuthenticationScheme, AzureADDefaults.AuthenticationScheme)
             .RequireAuthenticatedUser().Build();
         options.Filters.Add(new AuthorizeFilter(policy));
     });
    
  3. Run and login

Expected behavior

The blazor app should show the logged in user but blazor in particular seems unable to do so.

looking at the output [with some static file stuff removed]

        Request starting HTTP/2 GET https://localhost:5001/
  info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
        Executing endpoint '/_Host'
  info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[3]
        Route matched with {page = "/_Host", area = "", action = "", controller = ""}. Executing page /_Host
  info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[1]
        Authorization was successful.
  info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[103]
        Executing an implicit handler method - ModelState is Valid
  info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[104]
        Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult.
  info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[1]
        Authorization was successful.
  info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[4]
        Executed page /_Host in 137.3536ms
  info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
        Executed endpoint '/_Host'
  info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
        Request finished in 175.8487ms 200 text/html; charset=utf-8
  info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
        Request starting HTTP/2 POST https://localhost:5001/_blazor/negotiate text/plain;charset=UTF-8 0
  info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
        Executing endpoint '/_blazor/negotiate'
  info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
        Executed endpoint '/_blazor/negotiate'
  info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
        Request finished in 17.318ms 200 application/json
  info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
        Request starting HTTP/1.1 GET https://localhost:5001/_blazor?id=NhnoRIeHHoiihpkIjnUnCg
  info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
        Executing endpoint '/_blazor'
  info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
        Authorization failed.

It seems like authorization is successful when serving the _host but then fails later.

Additional context

There are few significant notes about this setup. AzureAD and AzureADBearer is used to use the AzureAD challenge if no auth is provided. In other words, you're ment to be able to open the app in a browser and enter the user login flow, or for a script to make a call to an api using a bearer token as well.

This means the auth filter must specify the Bearer auth first and the AzureAD second, otherwise the Bearer auth overwrites the AzureAD challenge response as user browses to a page. (at least this is my understanding)

i notice though that the log only prints out one auth middleware at the end of the log there, so i wonder if blazor only checks the first auth middleware, the bearer in my case, witch isn't authorized while missing the auth middleware that actually is authorized, i'm just guessing here though

The first request looks like this and calls both auth middlewares

info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint '/_Host'
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[3]
      Route matched with {page = "/_Host", area = "", action = "", controller = ""}. Executing page /_Host
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
      Authorization failed.
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[3]
      Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
      Executing ChallengeResult with authentication schemes (AzureADBearer, AzureAD).
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[12]
      AuthenticationScheme: AzureADJwtBearer was challenged.
info: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[12]
      AuthenticationScheme: AzureADOpenID was challenged.
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[4]
      Executed page /_Host in 474.51980000000003ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint '/_Host'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 580.8627ms 302

i'm using the latest vs preview and 3.0.100 of the dotnet sdk

This issue also seems a bit similar to #13709 but not quite the same

@aL3891 aL3891 changed the title Blazor /w multiple auth middleware Blazor with multiple auth middleware Oct 11, 2019
@javiercn javiercn added the area-blazor Includes: Blazor, Razor Components label Oct 11, 2019
@javiercn
Copy link
Member

@aL3891 Thanks for contacting us.

Does the issue happen when you are trying to render the initial page or after the first page has rendered and the Blazor app is trying to initialize?

Blazor simply uses whatever user is on HttpContext.User at the time it renders the initial document (for prerendering) or whatever user is in HubContext.User when it starts the circuit.

I suspect you have some miss-configuration by which your user is not being correctly setup in one of those two situations. To troubleshoot the issue you can do a couple of things:

  • Set a breakpoint on _Host.cshtml and validate that the user is correct.
  • Add an inline middleware after app.UseAuthentication() and check that HttpContext.User is what you expect.

The issue at the end of your comment is unrelated.

For more info check https://docs.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.0&tabs=visual-studio

@mkArtakMSFT mkArtakMSFT added question Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. investigate and removed question labels Oct 11, 2019
@aL3891
Copy link
Author

aL3891 commented Oct 11, 2019

This issue happens after the intial render. _host and other non-blazor routes does authenticate correctly and does have the user set. It's only blazor components that do not, and only when multiple auth middleware is used. Disabling bearer auth causes everything to work correctly, as does setting
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
instead of services.AddAuthentication(options => { options.DefaultChallengeScheme = AzureADDefaults.AuthenticationScheme; }) but doing so causes bearer auth to fail. this tells me that there is no issue with the AD setup or auth in general, but instead some issue with blazor (or signalr?) specifically

There may be some configuration that would solve this, that'd be great :) but i havent found any

Did you try the steps i provided?

@aL3891
Copy link
Author

aL3891 commented Oct 11, 2019

as i mentioned, i have a suspicion that this is caused by blazor (or signalr) is not calling all the auth middlewares, only the first one. for regular routes, this is solved by

        services.AddControllersWithViews(options =>
        {
            var policy = new AuthorizationPolicyBuilder(AzureADDefaults.BearerAuthenticationScheme, AzureADDefaults.AuthenticationScheme)
                .RequireAuthenticatedUser().Build();
            options.Filters.Add(new AuthorizeFilter(policy));
        });

or by setting [Authorize(AuthenticationSchemes ="AzureAD,AzureADBearer")] on controllers/pages. This does not seem to be supported on blazor components though, doing so gives an exception

NotSupportedException: The authorization data specifies an authentication scheme with value 'AzureAD,AzureADBearer'. Authentication schemes cannot be specified for components.
Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.EnsureNoAuthenticationSchemeSpecified(IAuthorizeData[] authorizeData)
Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.IsAuthorizedAsync(ClaimsPrincipal user)
Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.OnParametersSetAsync()

Perhaps there is a way to specify the equivalent setting for blazor/signalr on a global level?

@javiercn
Copy link
Member

javiercn commented Oct 11, 2019

Ah, I think I see it now.

You don’t seem to be setting the default authentication scheme.

You need to set that in the authentication options.

Also, you can set the authorization options for blazor by adding metadata to the endpoints, you can do so by chaining the call after the call to MapBlazorHub

                endpoints.MapBlazorHub()
                    .RequireAuthorization(/* Policy */);

@aL3891
Copy link
Author

aL3891 commented Oct 11, 2019

I can't set the default scheme since that seems to mess up the multiple auth as i mentioned. Or atleast i have not found a way to get it to work when setting it.

Using RequireAuthorization on MapBlazorHub i can however pass in an AuthorizeAttribute with AuthenticationSchemes set, and this does force signalr to check the right auth middleware:

endpoints.MapBlazorHub().RequireAuthorization(new AuthorizeAttribute { AuthenticationSchemes = AzureADDefaults.AuthenticationScheme });

👍

I think that should be added to the blazor authorization docs as well since its useful to force all blazor communication to be authorized, especially for backoffice apps like the one i'm building

@javiercn javiercn removed investigate Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. labels Oct 14, 2019
@javiercn
Copy link
Member

@aL3891 I'm glad that worked out.

I'm closing the issue as there is nothing Blazor specific here. I've filed a docs issue to cover this topic as part of the routing docs.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 2, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

No branches or pull requests

3 participants