dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
34.51k stars 9.75k forks source link

BlazorBFFOidc and DevTunnel - Auth to MS Entra works for swagger SPA and nearly working for Blazor website #55537

Open swegele opened 1 week ago

swegele commented 1 week ago

Is there an existing issue for this?

Describe the bug

First let me say a big YAY! With https://localhost/ I got Aspire/Blazor/API auth working with MS Entra for Customers (preview). Thank you so much for this sample code and docs!


Side note: For the curious - after using this authority: oidcOptions.Authority = "https://-mydomain-.ciamlogin.com/-tenantid-/oauth2/v2.0/authorize";

...the code automatically tries to get OIDC meta-data from the wrong url. So I had to do this manually: oidcOptions.MetadataAddress = "https://-mydomain-.ciamlogin.com/-tenantid-/v2.0/.well-known/openid-configuration";


Anyway, now I have setup a dev-tunnel with 2 ports to reach BOTH the WebAPI swagger page AND the Blazor web site when I fire up Aspire. I start the dev-tunnel in aspire AppHost program.cs like so: var mydevtunnel = builder.AddExecutable("my-dev-tunnel", "c:/tools/devtunnel.exe", builder.AppHostDirectory, "host");

Credit to @SteveSandersonMS comment here Works great!. I can navigate to the devtunnel from a browser:

WebAPI

Blazor Website

I have quadruple checked all the combinations of Azure AppRegistration Redirect URLs - those are all fine.

I think it is related to how the code does the redirect to auth and back via the following:

LoginLogoutEndpointRouteBuilderExtensions and app.MapGroup("/authentication").MapLoginAndLogout();

But I can't figure it out.

I think this would be REALLY cool to prove/show that the dev Inner-Loop with Aspire can include DevTunnels. I'm half way there with it working for the WebAPI swagger/OpenAPI site.

cc: @guardrex https://github.com/dotnet/blazor-samples/issues/288

Expected Behavior

Just like it works with Swagger index.html page...the Blazor Website and DevTunnel should also work leaving me logged in to the site with the proper url after coming back from auth against MS entra.

Steps To Reproduce

See above

Exceptions (if any)

Authorization failed - failed to correlate

.NET Version

8.0.300-preview.24203.14

Anything else?

No response

mkArtakMSFT commented 1 week ago

Thanks for contacting us. It looks like there are a few issues here, so let's address those in order:

  1. When specifying the Authority, you shouldn't have to explicitly specify the MetadataAddress. We think this is due to you having specified wrong Authority. Try this instead: oidcOptions.Authority = "https://-mydomain-.ciamlogin.com/-tenantid-/oauth2/v2.0";
  2. As for the redirect failure, we believe you have to configure the return URL in your app configuration in the Azure Portal.

Let us know how this goes?

swegele commented 1 week ago

Thanks @mkArtakMSFT

Re: Issue 1:

I made the .Authority change as you indicated so now it is: oidcOptions.Authority = "https://xxxx.ciamlogin.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/oauth2/v2.0";

And when I click login the site gives this error

InvalidOperationException: IDX20803: Unable to obtain configuration from: 
'https://dotheinterviewcustomers.ciamlogin.com/-mytenantid-/oauth2/v2.0/.well-known/openid-configuration'

(It is adding a .../oauth2/... part in the path.

The Azure AppRegistration : Endpoints tab shows the following as the proper metadata url: https://dotheinterviewcustomers.ciamlogin.com/-mytenantid-/v2.0/.well-known/openid-configuration When I manually force it to use this in config then I get sent to MS to login.

EDIT: I just figured this was something to do with the MS Entra for Customers being in preview still?

swegele commented 1 week ago

@mkArtakMSFT

So this is interesting. On a whim I tried the authority to this: oidcOptions.Authority = "https://-mydomainhere-.ciamlogin.com/-mytenantid-/v2.0";

That works to get me past that metadata error. I no longer have to set the meta data url: YAY Cool - that solves Issue 1.


Now for Issue 2. When I site sends me to Microsoft for auth here is the URL:

https://-mydomainhere-.ciamlogin.com/-mytenantid-/oauth2/v2.0/authorize?client_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&redirect_uri=https%3A%2F%2Flocalhost%3A7237%2Fsignin-oidc&response_type=code...bla bla bla

Notice the query string variable "redirect_uri" is https://localhost/signin-oidc I'm not sure how that got set that way because I was viewing the site through the dev tunnel at https://r2xxxxxx-7237.usw3.devtunnels.ms/

MS Auth works and MFA works...but obviously when redirected back to localhost it blows up.

Here is my Azure AppRegistration redirect URL (from manifest):

"replyUrlsWithType": [
        {
            "url": "https://r2xxxxxx-7237.usw3.devtunnels.ms/signin-oidc",
            "type": "Web"
        },
        {
            "url": "https://localhost:7237/signin-oidc",
            "type": "Web"
        }
    ],

EDIT: Remember my swagger page is authorizing and redirecting just fine. I notice it sends the correct redirect_uri value to microsoft. So my gut says this Issue 2 is something to do with the LoginLogoutEndpointRouteBuilderExtensions not properly setting that value.

I wonder if this "TODO" is the culprit:

   private static AuthenticationProperties GetAuthProperties(string? returnUrl)
   {
       // TODO: Use HttpContext.Request.PathBase instead.
       const string pathBase = "/";
swegele commented 1 week ago

Update:

I tried changing the minimal API in several ways and no matter what I do the querystring variable redirect_uri that gets sent to microsoft is always https://localhost....

So just for giggles...I manually copied the correct value to that variable sent to microsoft: ...&redirect_uri=https%3A%2F%2Fxxxxxxxx-7237.usw3.devtunnels.ms%2Fsignin-oidc...

Hit enter. It did the auth. Then MFA. Then redirected back to my site and I get this error:

An unhandled exception occurred while processing the request.
OpenIdConnectProtocolException: Message contains error: 'invalid_client', error_description: 'AADSTS500112: The reply address 'https://localhost:7237/signin-oidc' does not match the reply address 'https://xxxxxxxx-7237.usw3.devtunnels.ms/signin-oidc'

So it looks like on the way out AND on the way in, the site OIDC is always using localhost instead of the devtunnel. If I DO NOT use the dev tunnel and simply go to the site like normal using localhost then everything works great.

swegele commented 1 week ago

It even ignores setting the querystring variable sent to MS even if I do this!

        return new AuthenticationProperties 
        {            
            RedirectUri = "https://xxxxxxxx-7237.usw3.devtunnels.ms/"
        };

I'm baffled.

swegele commented 1 week ago

This works! Manual set value in blazor Program.cs. This makes auth work front to back (which proves my AppRegistration was set correctly):

oidcOptions.Events = new OpenIdConnectEvents()
{
    OnRedirectToIdentityProvider = context =>
    {
        context.ProtocolMessage.RedirectUri = "https://xxxxxxxx-7237.usw3.devtunnels.ms/signin-oidc";
        return Task.FromResult(0);
     }
};
swegele commented 1 week ago

@mkArtakMSFT (sorry for many messages as I figured this out)

Issue 2 - Summary

OIDC to MS Entra works fine using aspire localhost link to the blazor app. But when I try same using a DevTunnel - the OIDC engine doesn't use the base path, but rather continues using localhost. True for both the "to MS" and the "back from MS" parts.

The only way I have gotten OIDC in blazor to FULLY work with devtunnel is to manually set BOTH of these uri:

FIRST (in program.cs of blazor server):

oidcOptions.Events = new OpenIdConnectEvents()
{
    OnRedirectToIdentityProvider = context =>
    {
        context.ProtocolMessage.RedirectUri = "https://xxxx-7237.usw3.devtunnels.ms/signin-oidc";
        return Task.FromResult(0);
    }
};

SECOND (in LoginLogoutEndpointRouteBuilderExtensions.cs):

 return new AuthenticationProperties 
 {            
     RedirectUri = "https://xxxx-7237.usw3.devtunnels.ms/"
 };
guardrex commented 1 week ago

@mkArtakMSFT ... ~If @swegele's working approach in the last comment becomes the accepted approach, I can add a section to the end of the article on this. @swegele will see if I have the explanation correct. 👂~

Based on Steve's comment 👇, I'll hold and watch 👁️.

SteveSandersonMS commented 1 week ago

We discussed, and are not sure these workarounds should be required. We think it should just work without these workarounds, hence marking for investigation.

swegele commented 1 week ago

We discussed, and are not sure these workarounds should be required. We think it should just work without these workarounds, hence marking for investigation.

Agreed @SteveSandersonMS. The workaround proved it "can" and "should" work which is comforting.
But just like the Swagger SPA correctly picks up the devtunnel host from the url...so should the blazor web app. This ignoring of host part of address happens both with F5 and ctrl+F5.

I'm on Aspire 8.0.0-preview.6 Microsoft.AspNetCore.Authentication.OpenIdConnect (8.0.4)

I set a breakpoint here:

OnRedirectToIdentityProvider = context =>
{
    context.ProtocolMessage.RedirectUri = "https://xxxxxxxx-7237.usw3.devtunnels.ms/signin-oidc";

Then walked backwards up the callstack - to here:

Microsoft.aspNetCore.Authentication.OpenIdConnect. private async Task HandleChallengeAsyncInternal(AuthenticationProperties properties)

image

Very strange that Context is showing address at localhost because look at the URL in the browser window address:

image