Blazor WebAssembly Obfuscation: Protecting Client-Side .NET Code
Open the network tab in Chrome DevTools while loading a Blazor WebAssembly application. You will see a sequence of file requests - the .NET runtime, the application assemblies, the dependencies. Every one of those files is being downloaded to the browser. Every one of them contains your compiled .NET code. Every one of them can be saved, opened in ILSpy, and read.
This is not a misconfiguration or a security oversight. It is the fundamental architecture of how Blazor WebAssembly works. Understanding what that means for your application is the starting point for protecting it.
How Blazor WASM serves your code
When a user opens a Blazor WebAssembly application, the browser bootstraps a .NET runtime implemented in WebAssembly, then downloads your compiled assemblies to run them client-side. The entire application - your business logic, your algorithms, your string literals, your component hierarchy - executes in the browser rather than on a server.
In .NET 7 and earlier, the assemblies were served as standard .dll files. Any browser developer tool or download manager could save them directly.
In .NET 8 and later, Microsoft changed the delivery format. Assemblies are now packaged as .wasm files using the Webcil format - a WebAssembly wrapper around the standard .NET assembly. The file extension changed. The content did not. A .wasm file from a Blazor application can still be loaded into a .NET decompiler. The IL is still there, the metadata is still there, the string literals are still there.
The exposure is the same. The packaging is different.
Does AOT compilation change anything?
Blazor WebAssembly supports ahead-of-time (AOT) compilation, which compiles the IL directly to native WebAssembly bytecode before deployment. This is sometimes cited as making the assembly exposure less relevant.
It does not. Even with AOT compilation enabled, the original .NET DLL files are still shipped alongside the compiled WebAssembly. This is documented by Microsoft: Blazor requires the DLLs for reflection metadata and to support certain .NET runtime features. The AOT-compiled .wasm adds a native execution path; it does not remove the IL assemblies from the deployment.
Even setting the DLLs aside, WebAssembly bytecode is not meaningfully harder to analyze than IL. Tools like wasm-decompile and Ghidra can process WebAssembly binaries and produce readable output. AOT trades IL exposure for WebAssembly exposure - neither is opaque to a determined analyst.
A developer who enables AOT thinking it protects their code has added a performance optimization, not a security layer. Both the native WebAssembly and the original IL assemblies are present in the browser's download. For a deeper treatment of what Native AOT does and does not protect against, see the Native AOT vs obfuscation guide coming in a later section of this cocoon.
Blazor Server is a completely different story
Before going further it is worth drawing a clear boundary, because conflating the two Blazor hosting models is a common source of confusion.
Blazor Server keeps all .NET code on the server. The browser communicates with the server over a SignalR connection and receives only HTML updates. There are no .NET assemblies in the browser, no IL to download, no assembly to open in ILSpy. Your code never leaves the server. From a client-side code exposure perspective, Blazor Server is equivalent to any other server-rendered web application.
Blazor WebAssembly is the opposite. Everything runs in the browser. Your code is on the client.
The rest of this page applies exclusively to Blazor WebAssembly. If you are using Blazor Server, obfuscation is not relevant for client-side code exposure - focus on standard server-side security practices instead.
A note on the platform itself
I remember learning about Blazor for the first time and being struck by the ambition of the idea. It reminded me of an older project - a tool that let developers write Flash applications in C#, compiling CIL to Flash bytecode so you could design forms in Visual Studio and get a .swf as output. A genuinely innovative concept that never found its audience. When I saw Blazor WASM, I had a similar thought: the idea is compelling, but developers are deeply comfortable with JavaScript and TypeScript, and that comfort is hard to displace.
The security irony is that Blazor WASM has the most exposed threat model of any .NET deployment. The code that was always protected behind a server is now running in the browser, and the same property that makes .NET readable by decompilers - the preservation of types, names, and structure in IL - applies just as fully here. The binary is served over public HTTP to anyone who visits the URL. There is no access control, no installation step, no physical proximity requirement. The attack surface is as wide as your user base.
Blazor-specific failure modes when applying obfuscation
Generic obfuscation advice does not transfer cleanly to Blazor WASM. There are two specific failure modes that will break your application if not addressed.
Component parameters. Blazor resolves [Parameter]-decorated properties by name at runtime. When a parent component passes a value to a child component - <MyComponent Title="Hello" /> - the framework looks up the property named Title on MyComponent via reflection. If an obfuscator renames Title to something meaningless, the parameter silently stops receiving values. The component renders but shows no data. No exception is thrown. This is a hard-to-debug failure mode that has led many developers to abandon obfuscation entirely after one broken production build.
With ArmDot this is handled automatically. [Parameter]-decorated properties are public by definition - the framework requires them to be. ArmDot does not rename public members by default. A standard Blazor component with public [Parameter] properties will have those parameters work correctly after obfuscation without any special configuration.
JavaScript interop methods. Blazor WASM applications commonly call .NET methods from JavaScript using DotNet.invokeMethodAsync(). The target method must be decorated with [JSInvokable] and is identified at the JavaScript call site by its string name:
await DotNet.invokeMethodAsync('MyAssembly', 'ProcessData', payload);If the obfuscator renames ProcessData, the JavaScript call fails at runtime with a cryptic error - the method no longer exists under the name JavaScript is looking for. This is a Blazor-specific pitfall not covered in most obfuscation documentation.
The fix is to explicitly exclude [JSInvokable] methods from renaming:
[JSInvokable]
[ArmDot.Client.ObfuscateNames(Enable = false)]
public static Task<string> ProcessData(string payload)
{
// This method is called from JavaScript by name - must not be renamed
}Alternatively, apply [ObfuscateNames] at assembly level and add [ObfuscateNames(Enable = false)] specifically to each [JSInvokable] method. Either way, the rule is: if JavaScript calls a .NET method by string name, that method's name must be preserved.
Should you move logic to the server instead?
This is the most important question to ask before applying obfuscation to a Blazor WASM app, and the honest answer is: sometimes yes.
For many Blazor WASM applications, the architecturally correct response to client-side code exposure is to move sensitive logic to a server-side API and call it over HTTPS. A pricing algorithm, a proprietary validation routine, a license check - if the logic can be expressed as an API call without unacceptable latency, and the app already has a backend, keeping that logic server-side is often simpler and more robust than obfuscating it client-side.
Move logic to the server when: the logic is the primary thing you want to protect, an API call is architecturally straightforward, and you have a backend already.
Use obfuscation when: the app must run offline or with intermittent connectivity, the protection concern is broader than a single algorithm (application structure, embedded credentials, brand), the logic is too tightly coupled to the UI to extract cleanly, or moving everything server-side would require a rebuild that is not feasible.
For many Blazor WASM applications the right answer is both: move genuinely sensitive algorithms server-side, and obfuscate the client code that remains to protect the application structure, strings, and component logic from casual inspection.
Applying obfuscation to a Blazor WASM project
The MSBuild configuration is essentially identical to the general .NET pattern. Add ArmDot.Client and ArmDot.Engine.MSBuildTasks via NuGet, then add the protection target to your .csproj:
<Target Name="ProtectBeforePublishing"
AfterTargets="AfterCompile"
BeforeTargets="BeforePublish">
<ItemGroup>
<Assemblies Include="$(ProjectDir)$(IntermediateOutputPath)$(TargetFileName)" />
</ItemGroup>
<ArmDot.Engine.MSBuildTasks.ObfuscateTask
Inputs="@(Assemblies)"
ReferencePaths="@(_ResolveAssemblyReferenceResolvedFiles->'%(RootDir)%(Directory)')"
SkipAlreadyObfuscatedAssemblies="true" />
</Target>Apply protection attributes in your code:
// Assembly-wide renaming and string encryption
[assembly: ArmDot.Client.ObfuscateNames]
[assembly: ArmDot.Client.HideStrings]
// Virtualize high-value methods
public class LicenseService
{
[ArmDot.Client.VirtualizeCode]
public bool ValidateKey(string key)
{
// ...
}
}
// Preserve JSInvokable methods from renaming
[JSInvokable]
[ArmDot.Client.ObfuscateNames(Enable = false)]
public static Task<string> GetPublicData()
{
// Called from JavaScript - name must be preserved
}For a complete step-by-step walkthrough with build output and decompiler screenshots, see: How to obfuscate a Blazor App →
What the protected output looks like
After obfuscation, a developer opening the application's .wasm assembly in ILSpy sees renamed identifiers and obfuscated method bodies rather than readable C# source. For virtualized methods, the output is the VM interpreter loop - the original logic is absent from the IL entirely.
The strings that were previously visible in the network-fetched assembly - API endpoints, internal identifiers, configuration values - are replaced by decryption routines.
The exposure through the browser network tab is unavoidable - the assembly must be downloaded to run. What obfuscation changes is what that assembly reveals to anyone who downloads and examines it.
Related: Why .NET Code Is Vulnerable to Reverse Engineering → - the foundational explanation of why CIL metadata is so readable, with Blazor WASM as the most acute case of this general vulnerability.
For developers building healthcare or financial applications in Blazor WASM - patient portals, financial dashboards, apps handling regulated data - the exposure of client-side code has compliance implications beyond IP protection. HIPAA and GDPR both address the protection of sensitive data in applications. Obfuscation is one layer of a defence-in-depth approach for regulated Blazor WASM deployments.
Back to: .NET Obfuscation Platforms →
Protect your Blazor WASM application with ArmDot
ArmDot integrates into Blazor WebAssembly builds via NuGet and MSBuild, handling the [Parameter] preservation automatically and requiring only explicit exclusion of [JSInvokable] methods. Free trial available - protected assemblies stop working after two weeks.
