I am trying to create cookie while login with a user in Blazor web server app but I am bit lost

845 Views Asked by At

I did check a lot of videos and tutorials on how to do in this days and now I am literally confused from how many options possible to achieve it, yet none really work well for me. I'm just trying to understand what is the best way for create a simple program structure where a user after login into admin page set creation of a cookie that could expire in 10 minute, but can get refreshed on every access to a page with authorize attribute. That's without the use of Entity Framework btw. In this days I was experimenting it and I did menage to create cookie using controllers, is that the correct way to create cookie calling the controller function and let it create cookie with await HttpContext.SignOutAsync ? Otherwise I am open to any other suggestion but not point me again to documentations because I did try to understand from there but I get only more and more confused. btw I am working on Net 6.0 at the moment. Thank you for anyone that will point me out on some good resource to understand better this process.

As explained up I did check documentation and tried with the controller method but I want to know if I am doing it right or is a better way in Blazor to menage the user authorization and authentication ? Like ProtectedSessionStorage?

2

There are 2 best solutions below

6
Kurt Hamilton On BEST ANSWER

You cannot set cookies from a Blazor server-side session. You do not have access to the HttpContext and there isn't the traditional request/response cycle that you can use to return cookies in the response headers.

The approach I've has success with is to set up a login form in the usual Blazor way. If the login passes validation then I redirect to a traditional razor page (.cshtml file) that does have access to the HttpContext. After the cookie has been set I then redirect to the post-login page.

The same approach can be taken with logging out.

Below is a basic overview of the process.

<Blazor>
  @page "/login"
  - Render login form
  - Validate user input
  - Failure?
    - Display error message    
  - Success?  
    - Navigate to /dologin
</Blazor>

<Razor>
  @page "/dologin"
  - validate request
  - set auth cookie
  - navigate to /post-login
</Razor>

<Blazor>
  @page "/post-login"
  - validate cookie
  - continue session
  - ...
  - navigate to /logout
</Blazor>

<Razor>
  @page "/logout"
  - unset cookie
  - navigate to post-logout page
</Razor>

<Blazor>
  @page "/post-logout"
  - continue session
</Blazor>

Obviously this is a simplified representation, and doesn't go into any detail about post-login/logout redirect logic. Nor does it cover what abstraction you are using for auth management. In my project I use SignInManager from Microsoft.AspNetCore.Identity, but that is simply my choice of implementation and not related to my choice of workflow.

Edit

As described above, after I have validated the username and password, I navigate to the page that sets the auth cookie. Now because this is just another URL, we need to have a trusted way of identifying the authenticated user. We don't want to pass the username and the password in the URL for security reasons. To work around this I create a one-time token in my database after the credentials are validated and associate it with the user and use that to identify the user. I set the token to expire after a short time to improve security.

Relevant steps of the workflow:

<Blazor>
  @page "/login"
  - Valid credentials?
    - Create token for user in database
    - Navigate to /dologin?token=ABC123
</Blazor>

<Razor>
  @page "/dologin"
  - Get token from query string
  - Retrieve token from database
  - Token valid?
    - Delete token from database
    - Set auth cookie
</Razor>
0
Imre Pühvel On

Your use of controllers with cookies is generally correct and straightforward.

As already mentioned, you really cannot use Blazor normal components for settings cookies, as you don't have access to HttpContext. To set a cookie, you need a HTTP call from browser to acquire and persist the cookie on client side. Usually common WebAPI service is a good choice here.

What will NOT work for setting cookies:

  • you cannot call the cookie setting logic directly from interactive blazor serverside component, as blazor components live on long-living signalR connection. Cookies don't travel inside http body or inside signalR pipe, but in HTTP headers.
  • You can technically call the authentication service using HTTPClient on serverside blazor component, but this will not work as your authentication cookie would end up in HttpClient response object on server, and not in client browser.

What would work:

  • use JSInterop or just plain client-side JS to pass credetials to authentication service using fetch, which will return a cookie for server. Every call to that domain. API and blazor HTTP calls alike will now include you r authentication state.
  • You could verify credentials with blazor and use JSInterop etc to just write the cookie manually using JS, so separate service and HTTP call to server would not be needed at all.

I would prefer the first option with service, because it is more universally usable, and could set cookies using HttpOnly flag, which is is a huge help to avoid your authenticated session from travelling to attackers hands in case of XSS vulnerabilities.

About ProtectedSessionStorage

This is nothing to do with cookies, but that could be a cookieless alternative that is relatively easy to implement. Just implement you own AuthenticationStateProvider instance to get/set your authentication state from/to ProtectedSessionStorage.

The important factor to consider is that ProtectedSessionStorage will require JSInterOP to communicate with storage on browser, and hence it will work only for Blazor interactive mode. That is, authentication state can only acquired once page is sent and SignalR connection is up. Should you want to upgrade to .net 8 and use static SSR render mode, prerendering and other newer tricks, this will get funky or just plain break.

It will also require more custom code if you also need to share the authentication state with non-blazor parts, i.e. some WebApi services. You could create JWT tokens or the like, but using plain old cookies do seem a good and simple choice that would work as well.