Dilemma with JWT Management in Blazor Server + Worker Service + Centralized WebAPI
Hello community!
I'm facing an architectural dilemma regarding JWT authentication management in a scenario where I have multiple types of clients and a single centralized WebAPI as the backend. I would really appreciate insights from anyone who has solved similar problems.
Context:
I have two main client applications:
- A Worker Service (background service, no UI), which will run on client machines as needed.
- A web application using Blazor Server.
- In the future, I also plan to add mobile apps.
All these applications must consume an ASP.NET Core WebAPI that is the only component responsible for accessing the database. This is to avoid race conditions, accidental data overwrites, or inconsistencies, centralizing all business logic and data access in the API.
The problem:
I want all applications to use JWT for authentication, without exposing the database directly to any client.
In Blazor Server, this has been especially challenging due to its circuit model and the lifecycle of DI services.
I tried creating an in-memory token store with a simple interface, like:
public interface ITokenProvider
{
string? GetToken();
void SetToken(string? token);
}
and an implementation that just keeps the token in a private variable.
However, I've found that Blazor Server creates new instances of my TokenProvider for each injection into components or DI services, even when registered as Scoped.
This causes the token to be lost when navigating or when a new circuit is created—different parts of the app see different instances (and thus, different token values).
If I switch to Singleton, all users share the same token, which is a security risk and not acceptable.
I can't find a clean way to share the token across all components and services in Blazor Server, without having to pass the token manually in every request.
4 Replies
Clarification:
- This pattern (in-memory singleton token provider) works perfectly in Blazor WASM, because there I can inject the TokenProvider as a Singleton and it remains on the client machine, isolated per user.
- However, in Blazor Server, this is not possible due to the security risk of sharing a singleton instance between all connected users.
What I'm looking for:
- A pattern or strategy that allows for consistent and secure JWT management across all scenarios (web, worker, and future mobile).
- Avoid having to manually pass the token from the Blazor components on every request.
- Ensure JWT storage is secure and user-scoped in the web app, yet straightforward for the Worker Service.
- A solution that works with Blazor Server's DI and circuit lifecycle, where the classic scoped service pattern does not suffice.
How would you solve this scenario of multiple clients and a centralized WebAPI, keeping scalability and maintainability in mind?
Is there a recommended approach to share the token across all Blazor Server components and services, without losing it between navigations/circuits, and while keeping it secure?
Are there any patterns or resources you would recommend?
Thanks for reading and for any advice you can share!
I'm sure this discussion will help many others facing the same challenge.
We often use an identity provider such as Keycloak to manage authentication and authorization across multiple clients. This enables user accounts and service accounts to coexist while allowing us to define specific access scopes for each client. You can have access to the scope/role using the HttpContext.
intégration de .NET AspireKeycloak (préversion) - .NET Aspire
Découvrez comment utiliser l’intégration .NET AspireKeycloak, qui inclut à la fois les intégrations d’hébergement et de client.
I see, but HttpContext does not work inside SignalR because it does not send http requests but tunnel messages which doesn't include authorization headers. That's my problem, I wouldn't know when the user has been logged out or token expired if I can't even retrieve it on each request because the Custom Http Handler won't have access to the JS Interop runtime, so it can't read the LocalStorage or SessionStorage, that's the problem. And the scoped lifetime won't work because as soon as I inject a TokenStorage custom service it wipes out itself after changing the route of the page I'm visiting inside my app.
I ended up by setting the jwt manually with a static class in my services before sending the requests, it's the most decent solution I came up with.