Skip to content

Commit 6129f7e

Browse files
ui
1 parent d0561d5 commit 6129f7e

44 files changed

Lines changed: 2319 additions & 199 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
---
2+
title: "SIWE Authentication"
3+
sidebar_label: "SIWE Authentication"
4+
sidebar_position: 20
5+
description: "Implement Sign-In with Ethereum (EIP-4361) authentication in Blazor with JWT tokens and AuthorizeView"
6+
---
7+
8+
# SIWE Authentication
9+
10+
Sign-In with Ethereum (SIWE, [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361)) lets users authenticate by signing a message with their wallet instead of using passwords. Nethereum integrates SIWE directly with Blazor's `AuthenticationStateProvider` so you can use standard `<AuthorizeView>` and `[Authorize]` attributes.
11+
12+
## Authentication Flow
13+
14+
1. User connects wallet (see [Wallet Connection](guide-blazor-wallet-connect))
15+
2. Server generates a SIWE message with a nonce
16+
3. User signs the message in their wallet
17+
4. Server verifies the signature and creates a session (JWT or server-side)
18+
5. User gains `EthereumConnected` and `SiweAuthenticated` roles
19+
20+
## Two Roles, Two Levels
21+
22+
| Role | Meaning |
23+
|------|---------|
24+
| `EthereumConnected` | Wallet is connected (address known, no signature proof) |
25+
| `SiweAuthenticated` | User signed a SIWE message (cryptographic proof of ownership) |
26+
27+
## Blazor Server Setup
28+
29+
The server-side approach validates signatures directly without a REST API.
30+
31+
```bash
32+
dotnet add package Nethereum.Blazor
33+
dotnet add package Nethereum.Siwe
34+
```
35+
36+
Register services in `Program.cs`:
37+
38+
```csharp
39+
using Nethereum.Blazor;
40+
using Nethereum.Blazor.Siwe;
41+
using Nethereum.Siwe;
42+
using Nethereum.Siwe.Core;
43+
using Nethereum.UI;
44+
using Microsoft.AspNetCore.Components.Authorization;
45+
46+
builder.Services.AddSingleton<NethereumSiweAuthenticatorService>();
47+
builder.Services.AddSingleton<ISessionStorage, SessionStorageService>();
48+
49+
builder.Services.AddScoped<AuthenticationStateProvider,
50+
SiweAuthenticationServerStateProvider<User, SiweMessage>>();
51+
builder.Services.AddAuthorizationCore();
52+
```
53+
54+
Use in a component:
55+
56+
```razor
57+
@inject AuthenticationStateProvider AuthStateProvider
58+
@inject IEthereumHostProvider EthereumProvider
59+
60+
<AuthorizeView Roles="SiweAuthenticated">
61+
<Authorized>
62+
<p>Signed in as @context.User.Identity?.Name</p>
63+
<button @onclick="LogOut">Log Out</button>
64+
</Authorized>
65+
<NotAuthorized>
66+
<button @onclick="SignIn">Sign In with Ethereum</button>
67+
</NotAuthorized>
68+
</AuthorizeView>
69+
70+
@code {
71+
private async Task SignIn()
72+
{
73+
if (AuthStateProvider is SiweAuthenticationServerStateProvider<User, SiweMessage> siweAuth)
74+
{
75+
if (!EthereumProvider.Enabled)
76+
await EthereumProvider.EnableProviderAsync();
77+
78+
await siweAuth.AuthenticateAsync();
79+
}
80+
}
81+
82+
private async Task LogOut()
83+
{
84+
if (AuthStateProvider is SiweAuthenticationServerStateProvider<User, SiweMessage> siweAuth)
85+
{
86+
await siweAuth.LogOutUserAsync();
87+
}
88+
}
89+
}
90+
```
91+
92+
## Blazor WASM + REST API Setup
93+
94+
For WebAssembly apps, the SIWE signature is sent to a REST API that returns a JWT token. The token is stored in `localStorage`.
95+
96+
### REST API (Server)
97+
98+
The API generates nonces and validates signatures:
99+
100+
```csharp
101+
using Nethereum.Siwe;
102+
using Nethereum.Siwe.Core;
103+
104+
app.MapGet("/api/siwe/nonce", () =>
105+
{
106+
var nonce = SiweMessageService.GenerateNonce();
107+
return Results.Ok(new { nonce });
108+
});
109+
110+
app.MapPost("/api/siwe/verify", async (SiweVerifyRequest request,
111+
SiweJwtAuthorisationService jwtService) =>
112+
{
113+
var siweMessage = SiweMessageParser.Parse(request.Message);
114+
var validUser = SiweMessageService.IsMessageSignatureValid(
115+
request.Message, request.Signature);
116+
117+
if (!validUser) return Results.Unauthorized();
118+
119+
var token = jwtService.GenerateToken(siweMessage.Address);
120+
return Results.Ok(new { token });
121+
});
122+
```
123+
124+
### WASM Client
125+
126+
```csharp
127+
using Nethereum.Blazor.Siwe;
128+
using Nethereum.Siwe.Authentication;
129+
using Microsoft.AspNetCore.Components.Authorization;
130+
131+
builder.Services.AddSingleton<IAccessTokenService, LocalStorageAccessTokenService>();
132+
builder.Services.AddScoped<AuthenticationStateProvider,
133+
SiweAuthenticationWasmStateProvider>();
134+
builder.Services.AddAuthorizationCore();
135+
```
136+
137+
`SiweAuthenticationWasmStateProvider` stores the JWT in `localStorage` via `LocalStorageAccessTokenService`. On page reload, it reads the token and restores the authentication state.
138+
139+
## Protecting Routes
140+
141+
Use standard Blazor authorization attributes:
142+
143+
```razor
144+
@page "/dashboard"
145+
@attribute [Authorize(Roles = "SiweAuthenticated")]
146+
147+
<h3>Dashboard</h3>
148+
<p>Only visible to SIWE-authenticated users.</p>
149+
```
150+
151+
Or inline with `<AuthorizeView>`:
152+
153+
```razor
154+
<AuthorizeView Roles="EthereumConnected">
155+
<Authorized>
156+
<p>Wallet connected: @context.User.FindFirst(
157+
System.Security.Claims.ClaimTypes.NameIdentifier)?.Value</p>
158+
</Authorized>
159+
</AuthorizeView>
160+
161+
<AuthorizeView Roles="SiweAuthenticated">
162+
<Authorized>
163+
<p>Fully authenticated via SIWE.</p>
164+
</Authorized>
165+
</AuthorizeView>
166+
```
167+
168+
## NFT-Gated Access
169+
170+
Implement `IEthereumUserService` to restrict access based on token holdings:
171+
172+
```csharp
173+
using Nethereum.UI;
174+
175+
public class NFTGateService : IEthereumUserService
176+
{
177+
private readonly IEthereumHostProvider _provider;
178+
private readonly string _requiredNftContract;
179+
180+
public NFTGateService(IEthereumHostProvider provider, string requiredNftContract)
181+
{
182+
_provider = provider;
183+
_requiredNftContract = requiredNftContract;
184+
}
185+
186+
public async Task<bool> IsUserValidAsync(string address)
187+
{
188+
var web3 = await _provider.GetWeb3Async();
189+
var erc721 = web3.Eth.ERC721.GetContractService(_requiredNftContract);
190+
var balance = await erc721.BalanceOfQueryAsync(address);
191+
return balance > 0;
192+
}
193+
}
194+
```
195+
196+
The built-in `ERC721BalanceEthereumUserService` does this out of the box -- register it with the contract address:
197+
198+
```csharp
199+
builder.Services.AddSingleton<IEthereumUserService>(sp =>
200+
new ERC721BalanceEthereumUserService(
201+
sp.GetRequiredService<IEthereumHostProvider>(),
202+
"0xYourNFTContractAddress"));
203+
```
204+
205+
## Smart Contract Wallet Support
206+
207+
`SiweMessageService.IsMessageSignatureValid` supports:
208+
209+
- **EOA wallets** -- standard `ecRecover` signature verification
210+
- **ERC-1271** -- on-chain `isValidSignature` for deployed smart contract wallets
211+
- **ERC-6492** -- pre-deployment signature validation for counterfactual smart accounts
212+
213+
This is handled transparently. No additional configuration is needed.
214+
215+
## Full Starter Template
216+
217+
A complete SIWE template with REST API, Blazor Server, and Blazor WASM is available:
218+
219+
```bash
220+
dotnet new install Nethereum.Templates.Pack
221+
dotnet new nethereum-siwe -o MySiweDapp
222+
```
223+
224+
The template includes JWT generation, nonce management, MetaMask integration, and role-based authorization out of the box.
225+
226+
## Next Steps
227+
228+
- [Wallet Connection](guide-blazor-wallet-connect) -- connect wallets before authenticating
229+
- [Dynamic Contract Interaction](guide-blazor-contract-interaction) -- interact with contracts as the authenticated user
230+
- [Nethereum.Siwe](../protocols/nethereum-siwe) -- SIWE package reference (message building, parsing, verification)

0 commit comments

Comments
 (0)