Skip to content
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

[wasm] Blazor 303 responses, and fetch vs navigation #39365

Closed
dazinator opened this issue Jul 9, 2020 · 20 comments · Fixed by #41394
Closed

[wasm] Blazor 303 responses, and fetch vs navigation #39365

dazinator opened this issue Jul 9, 2020 · 20 comments · Fixed by #41394
Assignees
Labels
arch-wasm WebAssembly architecture area-System.Net.Http
Milestone

Comments

@dazinator
Copy link

Describe the bug

From a blazor wasm application,
Use HttpClient to submit a POST request to web api hosted on the same origin.
Have that API return a 303 response to redirect to an external URL

HTTP/1.1 303 See Other
Date: Wed, 08 Jul 2020 16:14:45 GMT
Server: Kestrel
Content-Length: 0
Location: https://login.microsoftonline.com/****************/saml2?SAMLRequest=***************&RelayState=***************
Set-Cookie: Saml2.hoU8e2aivVGd0X3xe_mLiReo=***************; path=/; secure; samesite=none; httponly
Vary: Origin
Access-Control-Allow-Origin: https://localhost:62808
X-Api-Status: Up
X-Tenant-Status: Up

Blazor automatically follows up on this redirect by submitting a GET request with the browser fetch api. However in this case, due to CORS policies on the external URL being redirected to, CROSS site requests are not allowed. I actually need to fully navigate the user to the new URL in the browser. Due to this Blazor fetch request fails with a CORS error, but only surfaces a generic "fetch has failed" exception back to user code.

Note: This seems to be different behaviour than what you see with ordinary MVC using a form to submit a POST request:

e.g from the ExternalLogin.cshtml in identity:

 <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
                        <div>
                            <p>
                                @foreach (var provider in Model.ExternalLogins)
                                {
                                    <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
                                }
                            </p>
                        </div>
                    </form>

This result of this POST will result in the browser being navigated to the LOCATION returned in the 303 redirect, which doesn't seem to happen if you use blazor EditForm, and handle the submit via HttpClient to do a POST.

Wondering if it would be more appropriate to either:

  1. Have HttpClient surface the 303 response directly somehow, to allow user code in blazor wasm to handle what to do - i.e navigate vs another http request to the new location?

Or

  1. Keep the existing logic of fetching from the redirected URL but only if its LOCATION has the same Origin as the original request (as then same CORS policies would likely apply). If the redirect is to a LOCATION completely external to the original origin, don't assume a fetch is ok, and perhaps perform a Navigate(reload: true) ?

To Reproduce

As per above, do a POST request from blazor wasm, and have the server return a 302 with a redirect to an external URL that prevents CORS requests.

Exceptions (if any)

Further technical details

  • ASP.NET Core version
  • Include the output of dotnet --info
  • The IDE (VS / VS Code/ VS4Mac) you're running on, and it's version
@dazinator dazinator changed the title Blazor 302 responses, and fetch vs navigation Blazor 303 responses, and fetch vs navigation Jul 9, 2020
@dazinator
Copy link
Author

dazinator commented Jul 9, 2020

Note: I also tried to implement a HttpClient MessageHandler to see if I could intercept the 303 response and perform a navigation instead, but alas, my MessageHandler doesn't get a look in on the 303 response.

    public class NavigateOn303Handler : DelegatingHandler
    {
        private readonly NavigationManager _navManager;

        public NavigateOn303Handler(NavigationManager navManager)
        {
            _navManager = navManager;
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            Console.WriteLine(nameof(NavigateOn303Handler) + " handler fired for " + request.RequestUri.ToString());          
            var result = await base.SendAsync(request, cancellationToken);

            // following code doesn't execute due to fetch exception for fetch send to LOCATION in 303 response happening higher in the stack
            Console.WriteLine(nameof(NavigateOn303Handler) + " handling response " + request.RequestUri.ToString());
            if (result.StatusCode == HttpStatusCode.SeeOther)
            {

                Console.WriteLine(nameof(NavigateOn303Handler) + " handling 303 response " + request.RequestUri.ToString());

                var location = result.Headers.Location;
                _navManager.NavigateTo(location.ToString(), true);
            }

            return result;
        }
    }


@javiercn
Copy link
Member

javiercn commented Jul 9, 2020

Thanks for contacting us.

We can't do any of the things you mention because the response is opaque to the code running on the page as per the CORS policy.

If you are calling and endpoint that redirects to a different origin you likely need to set the request mode to CORS. See here for details.

It is likely that the differences that you are seeing between your request and a form post are because of some headers or contents on the request you are sending vs a regular form post, you can tell this is the case specially if you see the browser make a preflight request, but this is just a hypothesis.

@dazinator
Copy link
Author

dazinator commented Jul 9, 2020

@javiercn - sorry I do not understand.
The request I am making via httpclient is to the same origin, and the response from the same origin is a 303 with a LOCATION header for a redirect. Some (not mine) code, then takes it upon itself to automaticaly follow up on this response, by automatically issuing a GET request using the browsers fetch api, and this looks like its dotnet.js related from the stack trace but its hard to tell. It's this "automatic fetch" that causes the CORS issue as the LOCATION is external and is expected to fail - it's not meant to accept cross site requests, it's meant to be navigated to (i.e it serves up a HTML document at that new origin)

The issue is, I need to submit a POST request initially, in order to get back a 303 response with a LOCATION uri, which I then need to "navigate in the browser" to, not send a cross site request - but when using blazor / httpclient I can't see any way to do this because it handles it all automatically and doesn't surface the 303 response, not even via a MessageHandler.

I think I'll have to construct an html form and not use blazor / http client for this, hopefully a normal Html form submit, will result in the browser submitting a POST request, and handling the 303 response as a normal navigation event, not a fetch request? (not ideal because this means I can't submit the post request via my http client pipeline in C## that does things like appends CORS headers, auth tokens etc). Still I raise this issue, because I don't think it's ideal that I have to break out of the blazor paradigm to try and solve this.

@javiercn
Copy link
Member

javiercn commented Jul 9, 2020

@dazinator thanks for the clarification. I think you might be able to construct the HttpClientHandler and set the redirections to false?

@dazinator
Copy link
Author

dazinator commented Jul 9, 2020

@javiercn - that looks like the way forward that I was missing - thank you very much.

When creating the new HttpClientHandler I need to be sure to make sure its set up the same as the default one that would usually be provided for me. I only want to set this "AutoRedirect" property not cause other unintended changes. I have put in place the following code now:

e.g:

            services.AddHttpClient(authHeadeHttpClient, (sp, client) =>
            {
                var navManager = sp.GetRequiredService<NavigationManager>();
                client.BaseAddress = navManager.ToAbsoluteUri("");             
            })             
            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
            {
                        AllowAutoRedirect = false,
                        // I want all other properties to be same as default that would normally be created for me.                      
           });

Does blazor / mono just use a new default instance of a HttpClientHandler() or does it do any additional configuration that I'd need to mirror that you are aware of?

Thanks

@dazinator
Copy link
Author

@javiercn AHHH damn.

System.PlatformNotSupportedException: Property AllowAutoRedirect is not supported.

@javiercn
Copy link
Member

javiercn commented Jul 9, 2020

@dazinator I believe it uses the default. It might be that you need to instantiate the wasmhandler directly, but I can't seem to find the type.

@pranavkm can help here. It can be the case that it is not possible to turn off auto-redirects and you might need to raise it up with the dotnet/runtime folks.

@pranavkm
Copy link
Contributor

pranavkm commented Jul 9, 2020

You should be able to configure the follow behavior on HttpRequestMessage:

requestMessage.SetBrowserRequestOption("redirect", "manual");

@dazinator
Copy link
Author

dazinator commented Jul 10, 2020

@pranavkm
Thanks - about to give this a go.

For anyone else that hits this, you currently need to add the following @using directive for that extension method:

@using Microsoft.AspNetCore.Components.WebAssembly.Http

@dazinator
Copy link
Author

dazinator commented Jul 10, 2020

@pranavkm
When I do this, it does now allow me to process the HttpResponseMessage (thanks) - but it has a status code of 0

@dazinator
Copy link
Author

@pranavkm

  • So the HttpResponseMessage has a 0 status code, and no Headers either. It seems like setting this BrowserRequestOptions causes everything on the HttpResponseMessage not to be set. I am not sure how best to get access to any info in the response in this scenario. I basically need to verify its a 303 status code, and grab the Location header value..

@ghost
Copy link

ghost commented Jul 11, 2020

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

@ghost ghost closed this as completed Jul 11, 2020
@dazinator
Copy link
Author

dazinator commented Jul 11, 2020

Err... it's not resolved! @javiercn can you re-open?

@pranavkm pranavkm reopened this Jul 13, 2020
@pranavkm
Copy link
Contributor

@kjpou1?

@ghost
Copy link

ghost commented Jul 14, 2020

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

@ghost ghost closed this as completed Jul 14, 2020
@dazinator
Copy link
Author

dazinator commented Jul 15, 2020

Looks like the 'resolved' label needs removing otherwise the bot will keep auto closing :-)

@pranavkm pranavkm reopened this Jul 15, 2020
@mkArtakMSFT mkArtakMSFT transferred this issue from dotnet/aspnetcore Jul 15, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Jul 15, 2020
@Dotnet-GitSync-Bot
Copy link
Collaborator

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@danmoseley
Copy link
Member

@mkArtakMSFT @pranavkm can one of you please clarify what you believe the bug is for us?

@ghost
Copy link

ghost commented Jul 15, 2020

Tagging subscribers to this area: @dotnet/ncl
Notify danmosemsft if you want to be subscribed.

@karelz karelz added the arch-wasm WebAssembly architecture label Jul 21, 2020
@karelz karelz changed the title Blazor 303 responses, and fetch vs navigation [wasm] Blazor 303 responses, and fetch vs navigation Jul 30, 2020
@marek-safar marek-safar removed the untriaged New issue has not been triaged by the area owner label Jul 31, 2020
@marek-safar marek-safar added this to the 5.0.0 milestone Jul 31, 2020
@kjpou1
Copy link
Contributor

kjpou1 commented Aug 4, 2020

We do not have access to the error code from the fetch API. Do not think this can be resolved from the browser fetch api to bubble this error up.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
arch-wasm WebAssembly architecture area-System.Net.Http
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants