Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpClient doesn't include cookies with requests in Blazor Webassembly app

I have a Blazor Webassembly app with a user service that is designed to hit an API to retrieve a user's detailed info. The service looks like this:

public class UserDataService : IUserDataService
{
    public readonly HttpClient _HttpClient;

    public UserDataService(HttpClient httpClientDI)
    {
        _HttpClient = httpClientDI;
    }

    public async Task<User> GetUserInfo()
    {
        try
        {
            return await _HttpClient.GetFromJsonAsync<User>("api/users/MyUserInfo");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            throw;
        }
    }
}

The API is specifically designed to read an encrypted cookie from the client request. This cookie contains the user's email address, and is used by the user info service to retrieve a more detailed set of user information.

[HttpGet("MyUserInfo")]
public User MyUserInfo()
{
    var myCookie = HttpContext.Request.Cookies.FirstOrDefault(c => c.Key == "MyCookie");

    var userMask = JsonConvert.DeserializeObject<AuthUserMask>(Protector.Unprotect(myCookie.Value));

    var user = UserService.Find(userMask.Email).FirstOrDefault();

    return user;
}

I'm able to verify that the cookie is there in the browser when I run the web app, but when the app makes the request to the API the cookie is not included. In fact the request doesn't include any cookies from the client at all.

enter image description here

I'm completely new to Blazor and I'm not sure what if any conventions exist for this type of scenario, but at the moment I'm just trying to get this new web app to work with our existing service. Is there a way to ensure the cookies are included? What could I be doing wrong?

Thanks in advance for the help.

EDIT

Here's the code that's creating the cookie. It's part of a larger method that verifies the user is authenticated, but this is the relevant part:

{
    var userJson = JsonConvert.SerializeObject(new AuthUserMask()
    {
        Email = user.Email,
        isActive = user.IsActive
    });

    var protectedContents = Protector.Protect(userJson);

    HttpContext.Response.Cookies.Append("MyCookie", protectedContents, new CookieOptions()
    {
        SameSite = SameSiteMode.None,
        Secure = true,
        Path = "/",
        Expires = DateTime.Now.AddMinutes(60)
    });

    HttpContext.Response.Redirect(returnUrl);
}

EDIT 2

Tried the following out in the UserDataService to see what would happen:

public async Task<User> GetUserInfo()
{
    try
    {
        _HttpClient.DefaultRequestHeaders.Add("Test", "ABC123");
        return await _HttpClient.GetFromJsonAsync<User>("api/users/MyUserInfo");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;
    }
}

Unfortunately the result is the same - the RequestCookieCollection is completely empty when it hits the API.

like image 705
Dumas.DED Avatar asked Sep 10 '20 14:09

Dumas.DED


People also ask

How does Blazor WebAssembly work with httpclient?

Blazor WebAssembly apps call web APIs using a preconfigured HttpClient service, which is focused on making requests back to the server of origin. Additional HttpClient service configurations for other web APIs can be created in developer code. Requests are composed using Blazor JSON helpers or with HttpRequestMessage.

How to send cookies to Blazor web API?

Using Blazor .net 6 style in Program.cs you need the following code: You don't need (and you shouldn't) specify the cookie. The correct cookie will be sent for you, just add BrowserRequestCredentials.Include in the message. On the server side, where you have your APIs, you need to set CORS allowing credentials.

What Blazor server coverage is available for calling web APIs?

This article has loaded Blazor Server coverage for calling web APIs. The Blazor WebAssembly coverage addresses the following subjects: Blazor WebAssembly examples based on an client-side WebAssembly app that calls a web API to create, read, update, and delete todo list items.

Can I return JSON from a Blazor web API?

A web API can be configured to return JSON even when an endpoint doesn't exist or an unhandled exception occurs on the server. For more information, see Handle errors in ASP.NET Core Blazor apps. This article has loaded Blazor Server coverage for calling web APIs. The Blazor WebAssembly coverage addresses the following subjects:


2 Answers

Add this

public class CookieHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);

        return await base.SendAsync(request, cancellationToken);
    }
}
like image 190
murat_yuceer Avatar answered Sep 29 '22 04:09

murat_yuceer


Using Blazor .net 6 style in Program.cs you need the following code:

builder.Services
    .AddTransient<CookieHandler>()
    .AddScoped(sp => sp
        .GetRequiredService<IHttpClientFactory>()
        .CreateClient("API"))
    .AddHttpClient("API", client => client.BaseAddress = new Uri(apiAddress)).AddHttpMessageHandler<CookieHandler>();

then you need the handler described by @murat_yuceer like:

namespace Client.Extensions
{
    public class CookieHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);

            return await base.SendAsync(request, cancellationToken);
        }
    }
}

You don't need (and you shouldn't) specify the cookie. The correct cookie will be sent for you, just add BrowserRequestCredentials.Include in the message.

On the server side, where you have your APIs, you need to set CORS allowing credentials.

Using .net 6 syntax you should already have in Program.cs:

app.UseCors(x => x.
  .AllowAnyHeader()
  .AllowAnyMethod()
  .AllowAnyOrigin()
);

but you need also AllowCredentials()

If you add AllowCredentials you obtain the following runtime error:

System.InvalidOperationException: 'The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the CORS policy by listing individual origins if credentials needs to be supported.'

So you need to specify the allowed origins, or a wildcard like this:

app.UseCors(x => x
    .AllowAnyHeader()
    .AllowAnyMethod()
    //.AllowAnyOrigin()
    .SetIsOriginAllowed(origin => true)
    .AllowCredentials()
);

And now all should works as expected.

like image 29
Nicola Biada Avatar answered Sep 29 '22 02:09

Nicola Biada