2

Unfortunately tutorials and prior StackOverflow answers for accessing cookies in Blazor Server seem to become invalidated with new .Net versions. For instance I can't get either of the following answers to work (and judging by the comments they don't in .net 6): How to use the HttpContext object in server-side Blazor to retrieve information about the user, user agent
How do I access HttpContext in Server-side Blazor?

I have a Program.cs file containing the following code:

..
builder.Services.AddHttpContextAccessor();
..  

it also has a app.MapPost method that sets a cookie using: HttpContext.Response.Cookies.Append

I can confirm the cookie works because in postman, I can successfully retrieve it when querying this app.MapPost method.

When attempting to query that method in a Login.Razor page, it works, however the cookie is not set. In this Login.razor, httpContextAccessor.HttpContext.Request.Cookies["my_cookie"] always returns null (even though the response itself is 200 OK, and I can read its content fine, there is no cookie retrievable via HttpContext).

This seems to have been a common issue, which judging by the two linked answers, was solved, but now in .net 6 appears broken again. How do I access HttpContext in Blazor Server .net 6 to access cookies that are sent in the query?

user4779
  • 645
  • 5
  • 14

2 Answers2

3

Please don't use of AddHttpContextAccessor() in Blazor Server.

I use of below approach in .Net6:

I modify the _Host.cshtml file as follows firstly.

@{
var myCookie = HttpContext.Request.Cookies["CookieName"];
}
<component type="typeof(App)" render-mode="ServerPrerendered" param-AccessToken="myCookie" />

I put the Cookie information into the myCookie variable and assign it to the component's param-AccessToken.

Then I go to the App.razor file and define the AccessToken variable as a Cascading Value. As follows:

<CascadingValue Name="AccessToken" Value="AccessToken">
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>
 @code{
    [Parameter]
     public string AccessToken { get; set; }
 }

Finally, any components that need Cookie info value just need to define it as a Cascade Parameter. For example, I create a component called ShowToken.razor and put its codes as below:

@page "/showtoken"

<p>This is a part of Cookie info @(AccessToken != null ? AccessToken.Substring(0,30) : "(null)")</p>


@code {
    [CascadingParameter(Name = "AccessToken")] public string AccessToken { get; set; }
}
Arani
  • 891
  • 7
  • 18
  • Thanks, for some reason this still doesn't seem to be working. The App.MapPost in Program.cs remains the same, is that correct? It still generates a cookie in postman. In the razor file I'm still generating a response from calling that same endpoint using "await Http.PostAsync(..)" which works, however no cookie is still being set. Is there some other way I'm supposed to set the cookie in the .razor file besides calling Http.PostAsync? Also in the App.razor just to confirm, I replace the code already there with your code (the default and also include a ? – user4779 Jan 18 '23 at 03:01
  • In BlazorServer only httpcontext is available with the correct value at the startup of the app. So whatever you want to do with cookies you can only do at the beginning (somewhere like _host.cshtml) after which the httpcontext will no longer contain a guaranteed correct value. You may get a value from httpcontext in the development environment, but after publishing the project in real environment, there is no guarantee that the value will be correct. It is not possible to set the cookie value in .razor files because you do not have access to a guaranteed httpcontext. – Arani Jan 18 '23 at 04:37
  • You can use any App.razor that you'd like, However, you have to put `` at the top of the App.razor. Sorry, I have put `` but it is not showing. But it can be seen in post editing mode. – Arani Jan 18 '23 at 04:43
  • Can you tell me exactly what information you need from the cookie? Is it related to user information? – Arani Jan 18 '23 at 04:47
  • yes its a authentication token. It needs to be set at runtime if the user registers whilst the app is running, so is this essentially not possible? – user4779 Jan 18 '23 at 04:51
  • I implemented a Blazor Server app based on Cookie Authentication https://github.com/Kamalifar/BlazorServerCookieAuthentication-master. I set the cookie in the startup.cs and I access the user info in the services by AuthenticationStateProvider https://github.com/Kamalifar/BlazorServerCookieAuthentication-master/blob/d54a943938c28f8cf6d5315a108ee67469aec450/Services/UserInfoService.cs#L22. I hope that it help you. – Arani Jan 18 '23 at 04:57
  • thank you, this seems to be almost exactly what im looking for! Just a couple questions -> Can authentication cookies be set and re-written at runtime or only on program startup? And would it be possible to remove the EF Core dependency and replace it with raw postgresql? – user4779 Jan 18 '23 at 05:01
  • Only on program startup. For more info about HttpContext valid value in Blazor Server you can refer to https://blog.lhotka.net/2022/03/16/ASPNET-Core-and-Blazor-Identity-and-State – Arani Jan 18 '23 at 05:10
  • so theres essentially no way to get and set cookies at runtime in blazor? – user4779 Jan 18 '23 at 06:00
  • 1
    Yes and No. You can set a cookie in Blazor Server at runtime, However, Just in startup of app and you can get the cookie at runtime everywhere with `CascadingValue` for component and with `AuthenticationStateProvider` for class. – Arani Jan 18 '23 at 06:29
0

In .net6 and above you can use Blazor State Management.

  1. Use localStorage to keep the state between tabs and browser instances.
  2. Use sessionStorage to only keep the state while the tab/browser is open.

localStorage would be the best choice for an AccessToken.

Inject wherever you want to access the storage. Components/Pages are okay to use.

[Inject] ProtectedLocalStorage localStorage {get; set;}

or

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage localStorage

Save your AccessToken as follows:

await localStorage.SetAsync("AccessToken", "123");

To retrieve the value:

ProtectedBrowserStorageResult<string> storedResult = await localStorage.GetAsync<string>("AccessToken");
if (storedResult.Success)
{
   string AccessToken = storedResult.Value.ToString();
}

Warning: Because this uses Javascript behind the scenes, you will get a Pre-Render error if you use this before the component has rendered. Two choices:

In _Host.cshtml change the render-mode from "ServerPrerendered" to "Server"

        <component type="typeof(App)" render-mode="Server" />
        <component type="typeof(HeadOutlet)" render-mode="Server" />

Or, retrieve the value via OnAfterRenderAsync

 protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            ProtectedBrowserStorageResult<string> storedResult = await localStorage.GetAsync<string>("AccessToken");
            if (storedResult.Success)
            {
                string AccessToken = storedResult.Value.ToString();
            }
            
        }
        await base.OnAfterRenderAsync(firstRender);
    }
John Rah
  • 1,757
  • 1
  • 14
  • 13