1

I am building a Blazor server website in .NET 8 and I am trying to add cookie authentication without using the identity scaffolding of .NET 8.

I used the Identity scaffolding as a guideline to see how Microsoft does it and got it working, but still encountered 2 issues:

When navigating to the homepage on https://localhost:7167/, this code is hit in the App.razor

private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/account")
      ? null
      : InteractiveServer;

which is used to set the rendermode on:

<body>
    <Routes @rendermode="RenderModeForPage" />
    <script src="_framework/blazor.web.js"></script>
</body>

and

<HeadOutlet @rendermode="RenderModeForPage" />

based on that code the initial rendering will be done as Interactive server.

I then click the login button:

 <NavLink class="nav-link" href="account/login">
     <RadzenButton Text="Aanmelden" Icon="account_circle" Size="ButtonSize.Medium" ButtonStyle="ButtonStyle.Primary" />
 </NavLink>

Which takes me to https://localhost:7167/account/login and this code is executed:

public async Task LoginUser()
{
    try
    {
         var session = await AuthService.SignInAsync(Input.Email, Input.Password);           

          var user = CreateIdentity(session.ExternalUserId, session.Claims["email"].ToString(), session.Claims);

          var authProperties = new AuthenticationProperties()
              {
                  AllowRefresh = true,
                  ExpiresUtc = DateTime.UtcNow.AddMinutes(5),
                  IsPersistent = true,
              };

          await Httpcontext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user, authProperties);

          Httpcontext.User = user;

          NavigationManager.NavigateTo("");
    }
    catch (Exception ex)
    {
        await HandleExceptionAsync(ex);
    }
}

Problem #1

Here I get a null reference exception on HttpContext, which is normal since I am still in rendering mode InteractiveServer.

This is a first point I do not understand, since when using the Identity scaffolding the navigation is done exactly the same and there the App.razor is hit again to check which render mode needs to be applied for /account/login and it is rendered as static SSR.

Problem #2

When I manually navigate to https://localhost:7167/account/login, the App.razor is hit and the page is properly rendered as static. I hit the login button and my cookie is set successfully, but I get this exception:

Exception of type 'Microsoft.AspNetCore.Components.NavigationException' was thrown.

when this line of code

NavigationManager.NavigateTo("");

is executed. I have tried all variations like NavigationManager.NavigateTo("/"), NavigationManager.NavigateTo("https://localhost:7167/") also with foreload set to true, but always the same exception.

To me, it looks like the navigation is done exactly the same in the scaffolded identity project, but then again the app.razor is hit when navigating to the login page so there must still be a difference somewhere I am just not seeing. My guess is somewhere in defining the routes or perhaps some magic in the SignInManager.

Any help on this would be greatly appreciated, I am starting to lose heart on this

1 Answer 1

0

So in the end I figured it out, thought I share it here for anyone that has the same issue.

Basically the answer is to create an Area for static components

it involved creating:

  1. A separate folder under Components
  2. Adding dedicated _Imports file for that area
  3. Adding dedicated Layout file for that area

enter image description here

The layout file basically only contains

@using xxxx.Components.Account.Shared
@layout AccountLayout

And the Layout will do the actual magic that turns a component from interactive server to static SSR

@if (HttpContext is null)
{
    <p>Loading...</p>
}
else
{
    @Body
}

@code {
    [CascadingParameter]
    private HttpContext? HttpContext { get; set; }

    protected override void OnParametersSet()
    {
        if (HttpContext is null)
        {
            // If this code runs, we're currently rendering in interactive mode, so there is no HttpContext.
            // The identity pages need to set cookies, so they require an HttpContext. To achieve this we
            // must transition back from interactive mode to a server-rendered page.
            NavigationManager.Refresh(forceReload: true);
        }
    }
}

basically whenever a navigation is done to a component in this area, the _Import file makes sure the new Layout is used.

Only static SSR components have an HttpContext, so if it is NULL it means current mode is InteractiveServer. NavigationManager.Refresh(forceReload: true) forces a page relead, which triggers the code in App.razor to identify which Rendering mode needs to be used.

That's it, hope it helps someone :)

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.