<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Prithwiraj Das</title>
    <description>The latest articles on DEV Community by Prithwiraj Das (@prithwiraj_das_8bc02a0871).</description>
    <link>https://dev.to/prithwiraj_das_8bc02a0871</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3838757%2Fc159d4c1-3090-4788-99aa-dc0caa6c64c5.jpg</url>
      <title>DEV Community: Prithwiraj Das</title>
      <link>https://dev.to/prithwiraj_das_8bc02a0871</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prithwiraj_das_8bc02a0871"/>
    <language>en</language>
    <item>
      <title>Feature Flags Are Not a Free Lunch</title>
      <dc:creator>Prithwiraj Das</dc:creator>
      <pubDate>Thu, 02 Apr 2026 04:06:29 +0000</pubDate>
      <link>https://dev.to/prithwiraj_das_8bc02a0871/feature-flags-are-not-a-free-lunch-460b</link>
      <guid>https://dev.to/prithwiraj_das_8bc02a0871/feature-flags-are-not-a-free-lunch-460b</guid>
      <description>&lt;p&gt;Every article about trunk-based development eventually arrives at the same recommendation: use feature flags. Gate incomplete work behind a toggle. Merge to main freely. Turn features on when they are ready.&lt;/p&gt;

&lt;p&gt;I have implemented this. And over the past few years, I have watched it create a category of technical debt that nobody warned me about.&lt;/p&gt;

&lt;p&gt;This is not an argument against feature flags. They are a useful tool. But the advice to "just use feature flags" leaves out what happens six months later when nobody cleans them up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94swpnaaj97o8w4ceotr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94swpnaaj97o8w4ceotr.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The plan is always clean
&lt;/h2&gt;

&lt;p&gt;You are building a new feature. It is not ready for all users yet. So you wrap it behind a feature flag. During development, the flag is off in production. Your code lives in main, safely dormant.&lt;/p&gt;

&lt;p&gt;When the feature is ready, you turn the flag on. Once all users have adopted it and the old code path is no longer needed, you remove the flag.&lt;/p&gt;

&lt;p&gt;Development, release, adoption, cleanup. Four steps.&lt;/p&gt;

&lt;p&gt;In practice, step four almost never happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why flags become permanent
&lt;/h2&gt;

&lt;p&gt;The first time a feature flag sticks around longer than planned, it feels harmless.&lt;/p&gt;

&lt;p&gt;Client A adopted the new feature. Client B did not want to move yet. The flag stays on for A and off for B. No big deal. We will clean it up next quarter.&lt;/p&gt;

&lt;p&gt;But next quarter, a new feature gets built on top of the flag-on code path. Now removing the flag means untangling the new feature from the old toggle. The effort is not justified, so it gets pushed again.&lt;/p&gt;

&lt;p&gt;Meanwhile, Client C onboards and gets a third configuration. The flag is no longer a temporary gate. It is load-bearing infrastructure that was never designed to be permanent.&lt;/p&gt;

&lt;p&gt;I have worked on codebases where feature flags that were supposed to last a sprint were still active two years later. Not because anyone decided to keep them. Just because removing them required more effort than anyone could justify in a sprint cycle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl0mgothxfs1h559i6byn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl0mgothxfs1h559i6byn.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  One flag doing too much
&lt;/h2&gt;

&lt;p&gt;Feature flags start simple: on or off.&lt;/p&gt;

&lt;p&gt;Over time, they absorb responsibilities they were never meant to carry.&lt;/p&gt;

&lt;p&gt;In one project, a single feature flag controlled two completely unrelated things: the data source for a report and the visibility of certain columns in the UI. The flag was originally created for the data source migration. The column visibility logic was added later because it was "related enough" and creating a separate flag felt like overkill.&lt;/p&gt;

&lt;p&gt;When the team needed to decouple those behaviors for different clients, it turned into a multi-sprint effort. The simple toggle had become a load-bearing wall in the application logic.&lt;/p&gt;

&lt;p&gt;This pattern repeats. Flags accumulate side responsibilities because it is faster to add a condition to an existing flag than to create and manage a new one. Each addition makes the eventual cleanup harder.&lt;/p&gt;




&lt;h2&gt;
  
  
  The client matrix problem
&lt;/h2&gt;

&lt;p&gt;Feature flags become genuinely dangerous when combined with client-specific targeting.&lt;/p&gt;

&lt;p&gt;In a multi-tenant application, it is common to enable features per client. Client A gets the new engine. Client B stays on the old one. Client C gets the new engine but not the new reporting module. Each combination is a distinct application state.&lt;/p&gt;

&lt;p&gt;Now multiply that across several flags. Flag A controls data source. Flag B controls UI behavior. Flag C controls a calculation engine. Different clients have different combinations. The number of possible states grows fast.&lt;/p&gt;

&lt;p&gt;Nobody tests all of those combinations. The QA team tests the most common configurations. But an untested combination is a production incident waiting to happen.&lt;/p&gt;

&lt;p&gt;I have spent hours debugging issues that turned out to be caused by an unexpected interaction between two flags that were never designed to work together. The code was correct for each flag individually. It just had not been tested in that particular combination.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wjpclvt4zj8qi6u6aud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wjpclvt4zj8qi6u6aud.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Migrating flag platforms
&lt;/h2&gt;

&lt;p&gt;Here is something that should set off alarm bells: if your team is migrating from one feature flag platform to another, the flag sprawl has already gotten out of hand.&lt;/p&gt;

&lt;p&gt;I have been through one of these migrations. The stated goal was modernization and cost reduction. The unstated reason was that the old platform had accumulated so many stale, undocumented flags that nobody trusted it anymore.&lt;/p&gt;

&lt;p&gt;What actually happened: every active flag had to be recreated in the new platform. Every client-specific targeting rule had to be rebuilt. Every code reference had to be updated. Tickets were created just to promote individual flags from dev to staging to production. The migration took multiple sprints and added zero user-facing value.&lt;/p&gt;

&lt;p&gt;And the new platform? Within months, it started accumulating the same kind of sprawl. Because the problem was never the platform. It was the lack of lifecycle governance.&lt;/p&gt;




&lt;h2&gt;
  
  
  The naming problem
&lt;/h2&gt;

&lt;p&gt;When multiple teams create feature flags independently, naming conventions diverge fast.&lt;/p&gt;

&lt;p&gt;In one codebase, I saw flags named like &lt;code&gt;team-feature-enable&lt;/code&gt; alongside flags named like &lt;code&gt;project-component-updates&lt;/code&gt;. Different teams, different conventions. Some flags used feature names. Others used team abbreviations. There was no central registry and no way to look at a flag name and understand what it controlled without reading the code.&lt;/p&gt;

&lt;p&gt;A new developer picking up a ticket that involved feature flags had to trace each flag through the codebase to understand what it did, which clients it applied to, and whether it was still active. That detective work added hours to what should have been straightforward tasks.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I think about now
&lt;/h2&gt;

&lt;p&gt;After living through flag chaos more than once, there are things I would set up before adopting feature flags at scale.&lt;/p&gt;

&lt;p&gt;Every flag gets a creation date, an owner, and an expected removal date. If the flag is still active past its expected date, it shows up somewhere visible and someone is accountable for it.&lt;/p&gt;

&lt;p&gt;One flag, one responsibility. If you are tempted to add a second behavior to an existing flag, create a new flag instead. The cost of managing an extra flag is far lower than untangling an overloaded one later.&lt;/p&gt;

&lt;p&gt;A naming convention before the first flag exists. The format matters less than the consistency.&lt;/p&gt;

&lt;p&gt;Dedicated time every quarter to audit active flags, remove stale ones, and consolidate duplicates. This never feels urgent, which is exactly why it needs to be scheduled.&lt;/p&gt;

&lt;p&gt;And if you have client-specific targeting, maintain a test matrix of the most common flag combinations. At minimum, document which combinations exist in production and make sure QA covers them.&lt;/p&gt;

&lt;p&gt;Feature flags are a useful tool. But without lifecycle discipline, they create the same kind of mess they were supposed to prevent. The chaos just moves to a different layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftuz8jk09a99tt96rr22q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftuz8jk09a99tt96rr22q.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>featureflags</category>
      <category>devops</category>
      <category>softwareengineering</category>
      <category>technicaldebt</category>
    </item>
    <item>
      <title>What Goes Wrong When You Switch Branching Strategies Mid-Flight</title>
      <dc:creator>Prithwiraj Das</dc:creator>
      <pubDate>Wed, 01 Apr 2026 19:50:27 +0000</pubDate>
      <link>https://dev.to/prithwiraj_das_8bc02a0871/what-goes-wrong-when-you-switch-branching-strategies-mid-flight-50l0</link>
      <guid>https://dev.to/prithwiraj_das_8bc02a0871/what-goes-wrong-when-you-switch-branching-strategies-mid-flight-50l0</guid>
      <description>&lt;p&gt;I have been part of at least four branching strategy migrations over a 12-year career.&lt;/p&gt;

&lt;p&gt;GitFlow to trunk-based. Bitbucket to GitHub. Sprint branches to release branches and back again.&lt;/p&gt;

&lt;p&gt;Every time, the decision to switch made sense on paper. Every time, the real problems showed up later, usually during the first production hotfix.&lt;/p&gt;

&lt;p&gt;Trunk-based development is the right long-term model for most teams. I am not arguing against it. But I have seen enough transitions go sideways that I wanted to write down what I have noticed, in case it is useful to someone going through the same thing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yue5t0l9hsh76oo5t4b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yue5t0l9hsh76oo5t4b.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The switch usually happens fast
&lt;/h2&gt;

&lt;p&gt;In most places I have worked, the decision to change branching strategies came from leadership during a broader initiative. A platform migration, a new CI/CD tool, a push to modernize.&lt;/p&gt;

&lt;p&gt;The intent is always good. But the rollout tends to be uniform. Every repo switches at once, regardless of whether each team is ready for the new workflow.&lt;/p&gt;

&lt;p&gt;The thing is, a branching strategy is not really an infrastructure decision. It is a team capability decision. It works when the team's release cadence, test coverage, and deployment setup are aligned with it.&lt;/p&gt;

&lt;p&gt;When those things are not in place yet, the new model introduces problems that the old model, for all its flaws, did not have.&lt;/p&gt;




&lt;h2&gt;
  
  
  The test gap
&lt;/h2&gt;

&lt;p&gt;Trunk-based development assumes main is always releasable. That is a strong assumption.&lt;/p&gt;

&lt;p&gt;It only holds if automated tests run in the pipeline as a blocking gate.&lt;/p&gt;

&lt;p&gt;In more than one team I have been part of, tests were encouraged but not enforced in CI. Code could land in main without passing automated validation.&lt;/p&gt;

&lt;p&gt;In a branch-based model, that is survivable. Bad code sits in a feature or develop branch. You catch it before it reaches production.&lt;/p&gt;

&lt;p&gt;In trunk-based, there is no buffer. Main is production. If the pipeline does not block on test failures, you are relying on code review alone to catch runtime bugs and regressions.&lt;/p&gt;

&lt;p&gt;I have seen this pattern repeat across organizations. The branching model changes. The test infrastructure does not follow.&lt;/p&gt;

&lt;p&gt;The pipeline is the enforcement mechanism. If it does not fail on failing tests, those tests are effectively optional.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fok0q4hfhlx9sjov40k0d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fok0q4hfhlx9sjov40k0d.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The hotfix that could not ship
&lt;/h2&gt;

&lt;p&gt;This is where it gets real.&lt;/p&gt;

&lt;p&gt;A critical bug surfaces in production. The fix is reviewed and ready.&lt;/p&gt;

&lt;p&gt;But main has other changes in it that are not production-ready. Unfinished features, untested integrations. Deploying the fix means deploying everything.&lt;/p&gt;

&lt;p&gt;In theory, trunk-based prevents this because main should always be clean. In practice, that takes time to build: small commits, good CI, feature flags for incomplete work. During the transition period, main is rarely in that state.&lt;/p&gt;

&lt;p&gt;I have been in a situation where a straightforward hotfix took over 24 hours to unblock. The pipeline was not set up to deploy from anything other than main. The development team, the engineering manager, and the DevOps team all had to coordinate to add hotfix branch support on the spot.&lt;/p&gt;

&lt;p&gt;For teams working on business-critical applications, whether it is healthcare, logistics, or e-commerce, this is especially painful. Hotfixes are not rare events. Access control bugs, data display issues, calculation errors. These come up regularly and need to ship the same day.&lt;/p&gt;

&lt;p&gt;A branching strategy that turns a one-line fix into a coordination exercise has a gap that needs addressing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59pxka3g9ipceq5g5ywd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59pxka3g9ipceq5g5ywd.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The pipeline is not always in your hands
&lt;/h2&gt;

&lt;p&gt;Something that rarely comes up in branching strategy blog posts: in many organizations, developers do not control their own deployment pipelines.&lt;/p&gt;

&lt;p&gt;The CI/CD setup is owned by a DevOps or platform team. Developers can push code, but they cannot change workflow triggers, deployment rules, or environment settings.&lt;/p&gt;

&lt;p&gt;This is a reasonable separation of concerns. But it creates a coordination bottleneck during transitions.&lt;/p&gt;

&lt;p&gt;When the team I was on needed hotfix deployments added to the pipeline, it was not something we could do ourselves. It required a request to DevOps, who had to evaluate the change across dozens of repositories. That is a fair concern on their end. But on the development side, there was a fix ready to go and no way to ship it.&lt;/p&gt;

&lt;p&gt;Branch naming conventions added another layer. The pipeline required specific patterns like &lt;code&gt;feature/TICKET-ID&lt;/code&gt; to trigger builds. Push a branch with a slightly different format, and the pipeline did nothing. No error, no warning. Just silence.&lt;/p&gt;

&lt;p&gt;That kind of thing is easy to solve with documentation. But it catches people during the early days of a migration.&lt;/p&gt;

&lt;p&gt;When developers cannot see or influence how the pipeline behaves, the branching strategy on paper and the branching strategy in practice are two different things.&lt;/p&gt;




&lt;h2&gt;
  
  
  Every repo ends up different
&lt;/h2&gt;

&lt;p&gt;One of the more disorienting things about a migration is that different repositories end up at different stages.&lt;/p&gt;

&lt;p&gt;Some repos followed a tag-based deployment model. Others deployed from main only. Some had manual dispatch. Others had strict branch naming triggers. The hotfix path that worked in one repo did not exist in another.&lt;/p&gt;

&lt;p&gt;During one incident, the tech lead mentioned that different architects had different expectations about the process. Some assumed one workflow, others assumed another. Everyone was working from a different mental model.&lt;/p&gt;

&lt;p&gt;Consistency matters more than having the perfect model. Three different "right" approaches across an organization is harder to manage than one imperfect approach that everyone understands.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7whx1g5666w87xikmj3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7whx1g5666w87xikmj3w.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I think about now
&lt;/h2&gt;

&lt;p&gt;After going through this a few times, there are things I look for before a branching strategy switch.&lt;/p&gt;

&lt;p&gt;Does the CI pipeline enforce tests as a hard blocker, not a suggestion?&lt;/p&gt;

&lt;p&gt;Is there a tested path to ship a hotfix to production without deploying everything in main?&lt;/p&gt;

&lt;p&gt;Can developers trigger common deployment operations without filing a ticket?&lt;/p&gt;

&lt;p&gt;Is the model consistent across repositories, or does each repo have its own workflow?&lt;/p&gt;

&lt;p&gt;Has anyone actually simulated a production hotfix under the new process?&lt;/p&gt;

&lt;p&gt;These are not exotic requirements. But in every migration I have been part of, at least a few of them were missing on day one.&lt;/p&gt;

&lt;p&gt;They always surfaced at the worst possible time.&lt;/p&gt;

&lt;p&gt;Trunk-based development is worth getting to. It just helps to make sure the foundation is there before making the switch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gbl1qyu2346260qhp2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gbl1qyu2346260qhp2a.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>devops</category>
      <category>git</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>What broke my JWT flow in Blazor Server and how I fixed it</title>
      <dc:creator>Prithwiraj Das</dc:creator>
      <pubDate>Mon, 23 Mar 2026 15:10:13 +0000</pubDate>
      <link>https://dev.to/prithwiraj_das_8bc02a0871/what-broke-my-jwt-flow-in-blazor-server-and-how-i-fixed-it-105o</link>
      <guid>https://dev.to/prithwiraj_das_8bc02a0871/what-broke-my-jwt-flow-in-blazor-server-and-how-i-fixed-it-105o</guid>
      <description>&lt;p&gt;&lt;em&gt;Three things I got wrong building my first production Blazor Server app, coming from a background in Angular and traditional ASP.NET.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Originally published on &lt;a href="https://medium.com/@prithwirajdas07/what-broke-my-jwt-flow-in-blazor-server-and-how-i-fixed-it-50df948a64dd" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The one thing you need to understand first
&lt;/h2&gt;

&lt;p&gt;In a standard HTTP app, every user interaction is a new request — new pipeline, new HttpContext, new everything. Blazor Server works differently: the initial page load is an HTTP request, but after that, everything runs over a persistent WebSocket connection (SignalR). There are no further HTTP requests.&lt;/p&gt;

&lt;p&gt;This means &lt;strong&gt;HttpContext is only reliably available during that first request&lt;/strong&gt; — once the SignalR circuit is established, it is either null or stale.&lt;/p&gt;

&lt;p&gt;Microsoft's documentation puts it plainly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"IHttpContextAccessor generally should be avoided with interactive rendering because a valid HttpContext isn't always available."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That one sentence explains everything below.&lt;/p&gt;




&lt;h2&gt;
  
  
  Issue 1 — User metadata cached at the wrong time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What I had:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;UserMetadataService&lt;/code&gt; loaded user auth data in the constructor using &lt;code&gt;Task.Run(...).Wait()&lt;/code&gt;. Load once, reuse everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserMetadataService&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IHttpContextAccessor&lt;/span&gt; &lt;span class="n"&gt;_authState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IUserAuthData&lt;/span&gt; &lt;span class="n"&gt;userAuthData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;UserMetadataService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IHttpContextAccessor&lt;/span&gt; &lt;span class="n"&gt;authState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="n"&gt;_authState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
        &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoadUserMetaData&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;LoadUserMetaData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_authState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
        &lt;span class="n"&gt;userAuthData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UserAuthData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked on first load. But for users whose session context needed to be read after circuit establishment — role-based access checks, tenant resolution — the cached value was stale, and for some users it was null entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixed it:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Converting &lt;code&gt;userAuthData&lt;/code&gt; from a cached property to a computed one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserMetadataService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IHttpContextAccessor&lt;/span&gt; &lt;span class="n"&gt;authState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IUserAuthData&lt;/span&gt; &lt;span class="n"&gt;userAuthData&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;get&lt;/span&gt;  
        &lt;span class="p"&gt;{&lt;/span&gt;  
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UserAuthData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
        &lt;span class="p"&gt;}&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Issue 2 — Reading HttpContext too late in TokenService
&lt;/h2&gt;

&lt;p&gt;This one took longer to find because it only failed intermittently and left almost nothing useful in the logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I had:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;TokenService&lt;/code&gt; held a reference to &lt;code&gt;IHttpContextAccessor&lt;/code&gt; and read &lt;code&gt;HttpContext&lt;/code&gt; inside &lt;code&gt;RefreshTokenAsync()&lt;/code&gt; — called whenever a new JWT was needed for an outgoing API request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TokenService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ITokenService&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IHttpContextAccessor&lt;/span&gt; &lt;span class="n"&gt;_httpContextAccessor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RefreshTokenAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_httpContextAccessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpContext&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;IsAuthenticated&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
        &lt;span class="p"&gt;{&lt;/span&gt;  
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User is not authenticated"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
            &lt;span class="n"&gt;_cachedToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
            &lt;span class="n"&gt;_navigationManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NavigateTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_navigationManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forceLoad&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
        &lt;span class="p"&gt;}&lt;/span&gt;  

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tokenDescriptor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SecurityTokenDescriptor&lt;/span&gt;  
        &lt;span class="p"&gt;{&lt;/span&gt;  
            &lt;span class="n"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsIdentity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;  
            &lt;span class="n"&gt;IssuedAt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
            &lt;span class="n"&gt;Expires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
        &lt;span class="p"&gt;};&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The constructor runs during initial circuit setup, before SignalR takes over. That is the one reliable window to read from HttpContext in a Blazor Server service.&lt;/strong&gt; Reading it anywhere else — from a method, a timer, a component event — is not safe.&lt;/p&gt;




&lt;h2&gt;
  
  
  Issue 3 — JwtTokenForwardHandler extending the wrong base class
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What I had:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JwtTokenForwardHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpClientHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="c1"&gt;// In Program.cs  &lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigurePrimaryHttpMessageHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceProvider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;  
    &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JwtTokenForwardHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;HttpClientHandler&lt;/code&gt; is the primary transport handler — it owns the socket and the connection pool. Registering it via &lt;code&gt;ConfigurePrimaryHttpMessageHandler&lt;/code&gt; tells &lt;code&gt;IHttpClientFactory&lt;/code&gt; to treat it as the transport layer, which means it gets &lt;strong&gt;pooled and shared across requests&lt;/strong&gt;. Any user-specific state on that handler can persist across request boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What fixed it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JwtTokenForwardHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DelegatingHandler&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ITokenService&lt;/span&gt; &lt;span class="n"&gt;_tokenService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;JwtTokenForwardHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ITokenService&lt;/span&gt; &lt;span class="n"&gt;tokenService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="n"&gt;_tokenService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpResponseMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="n"&gt;HttpRequestMessage&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_tokenService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCurrentTokenAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authorization&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;  
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AuthenticationHeaderValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="c1"&gt;// In Program.cs  &lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpMessageHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JwtTokenForwardHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;DelegatingHandler&lt;/code&gt; is outgoing middleware — it sits in front of the transport layer, adds the token per call inside &lt;code&gt;SendAsync&lt;/code&gt;, and passes control down the chain. No state is stored on the handler instance.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I took away from this
&lt;/h2&gt;

&lt;p&gt;All three of these patterns felt correct when I wrote them. They were correct in every HTTP-based context I had used them in. The shift Blazor Server requires is not obvious from reading the API surface — it only becomes clear once you understand that the circuit model fundamentally changes when HttpContext exists and what service lifetimes mean in that environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/security/server/additional-scenarios#ihttpcontextaccessorhttpcontext-in-aspnet-core-blazor-apps" rel="noopener noreferrer"&gt;IHttpContextAccessor/HttpContext in ASP.NET Core Blazor apps — Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/security/server/additional-scenarios" rel="noopener noreferrer"&gt;ASP.NET Core server-side and Blazor Web App additional security scenarios — Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory" rel="noopener noreferrer"&gt;Use IHttpClientFactory — .NET | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory-troubleshooting" rel="noopener noreferrer"&gt;Troubleshoot IHttpClientFactory issues — .NET | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>dotnet</category>
      <category>blazor</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
