0
# Authentication
1
2
SIWE (Sign-In with Ethereum) authentication integration for secure user authentication in RainbowKit applications.
3
4
## Capabilities
5
6
### RainbowKitAuthenticationProvider
7
8
Provider component that wraps your application to enable authentication functionality.
9
10
```typescript { .api }
11
/**
12
* Authentication provider component for SIWE integration
13
* @param props - Authentication provider configuration
14
* @returns React provider component for authentication
15
*/
16
function RainbowKitAuthenticationProvider<T>(
17
props: RainbowKitAuthenticationProviderProps<T>
18
): JSX.Element;
19
20
interface RainbowKitAuthenticationProviderProps<T> extends AuthenticationConfig<T> {
21
/** Whether authentication is enabled */
22
enabled?: boolean;
23
/** React children to wrap */
24
children: ReactNode;
25
}
26
27
interface AuthenticationConfig<T> {
28
/** Authentication adapter implementation */
29
adapter: AuthenticationAdapter<T>;
30
/** Current authentication status */
31
status: AuthenticationStatus;
32
}
33
34
type AuthenticationStatus = 'loading' | 'unauthenticated' | 'authenticated';
35
```
36
37
**Usage Examples:**
38
39
```typescript
40
import {
41
RainbowKitAuthenticationProvider,
42
createAuthenticationAdapter,
43
AuthenticationStatus,
44
} from '@rainbow-me/rainbowkit';
45
import { useState } from 'react';
46
47
function App() {
48
const [authenticationStatus, setAuthenticationStatus] = useState<AuthenticationStatus>('loading');
49
50
const authenticationAdapter = createAuthenticationAdapter({
51
getNonce: async () => {
52
const response = await fetch('/api/nonce');
53
return await response.text();
54
},
55
56
createMessage: ({ nonce, address, chainId }) => {
57
return `Sign this message to authenticate: ${nonce}`;
58
},
59
60
verify: async ({ message, signature }) => {
61
const response = await fetch('/api/verify', {
62
method: 'POST',
63
headers: { 'Content-Type': 'application/json' },
64
body: JSON.stringify({ message, signature }),
65
});
66
67
const verified = await response.json();
68
setAuthenticationStatus(verified ? 'authenticated' : 'unauthenticated');
69
return verified;
70
},
71
72
signOut: async () => {
73
await fetch('/api/logout', { method: 'POST' });
74
setAuthenticationStatus('unauthenticated');
75
},
76
});
77
78
return (
79
<RainbowKitAuthenticationProvider
80
adapter={authenticationAdapter}
81
status={authenticationStatus}
82
>
83
{/* Your app */}
84
</RainbowKitAuthenticationProvider>
85
);
86
}
87
```
88
89
### createAuthenticationAdapter
90
91
Factory function for creating authentication adapters with SIWE integration.
92
93
```typescript { .api }
94
/**
95
* Creates an authentication adapter for SIWE integration
96
* @param adapter - Authentication adapter implementation
97
* @returns Configured authentication adapter
98
*/
99
function createAuthenticationAdapter<T>(
100
adapter: AuthenticationAdapter<T>
101
): AuthenticationAdapter<T>;
102
103
interface AuthenticationAdapter<T> {
104
/** Fetches a nonce from your authentication server */
105
getNonce: () => Promise<string>;
106
/** Creates a message to be signed by the user */
107
createMessage: (args: CreateMessageArgs) => T;
108
/** Verifies the signed message on your server */
109
verify: (args: VerifyArgs<T>) => Promise<boolean>;
110
/** Signs out the user */
111
signOut: () => Promise<void>;
112
}
113
114
interface CreateMessageArgs {
115
/** Random nonce from server */
116
nonce: string;
117
/** User's wallet address */
118
address: string;
119
/** Current chain ID */
120
chainId: number;
121
}
122
123
interface VerifyArgs<T> {
124
/** The message that was signed */
125
message: T;
126
/** The signature from the user's wallet */
127
signature: string;
128
}
129
```
130
131
**Usage Examples:**
132
133
```typescript
134
import { createAuthenticationAdapter } from '@rainbow-me/rainbowkit';
135
import { SiweMessage } from 'siwe';
136
137
// SIWE (Sign-In with Ethereum) adapter
138
const siweAuthenticationAdapter = createAuthenticationAdapter({
139
getNonce: async () => {
140
const response = await fetch('/api/nonce');
141
return await response.text();
142
},
143
144
createMessage: ({ nonce, address, chainId }) => {
145
return new SiweMessage({
146
domain: window.location.host,
147
address,
148
statement: 'Sign in with Ethereum to the app.',
149
uri: window.location.origin,
150
version: '1',
151
chainId,
152
nonce,
153
});
154
},
155
156
verify: async ({ message, signature }) => {
157
const verifyRes = await fetch('/api/verify', {
158
method: 'POST',
159
headers: {
160
'Content-Type': 'application/json',
161
},
162
body: JSON.stringify({ message, signature }),
163
});
164
165
return Boolean(verifyRes.ok);
166
},
167
168
signOut: async () => {
169
await fetch('/api/logout', { method: 'POST' });
170
},
171
});
172
173
// Simple message adapter
174
const simpleAuthenticationAdapter = createAuthenticationAdapter({
175
getNonce: async () => {
176
return Math.random().toString(36).substring(2);
177
},
178
179
createMessage: ({ nonce, address }) => {
180
return `Please sign this message to authenticate: ${nonce}\nAddress: ${address}`;
181
},
182
183
verify: async ({ message, signature }) => {
184
// Implement your verification logic
185
return true;
186
},
187
188
signOut: async () => {
189
console.log('Signed out');
190
},
191
});
192
```
193
194
### useAddRecentTransaction
195
196
Hook for adding transactions to the recent transactions list (useful for authenticated flows).
197
198
```typescript { .api }
199
/**
200
* Hook for adding transactions to recent transactions list
201
* @returns Function to add transactions
202
*/
203
function useAddRecentTransaction(): (transaction: NewTransaction) => void;
204
205
interface NewTransaction {
206
/** Transaction hash */
207
hash: string;
208
/** Human-readable description */
209
description: string;
210
/** Number of confirmations (optional) */
211
confirmations?: number;
212
}
213
214
interface Transaction extends NewTransaction {
215
/** Transaction status */
216
status: 'pending' | 'confirmed' | 'failed';
217
}
218
```
219
220
**Usage Examples:**
221
222
```typescript
223
import { useAddRecentTransaction } from '@rainbow-me/rainbowkit';
224
import { useContractWrite, useWaitForTransaction } from 'wagmi';
225
226
function TokenTransfer() {
227
const addRecentTransaction = useAddRecentTransaction();
228
229
const { data, write } = useContractWrite({
230
// ... contract configuration
231
onSuccess(data) {
232
addRecentTransaction({
233
hash: data.hash,
234
description: 'Transfer tokens',
235
});
236
},
237
});
238
239
return (
240
<button onClick={() => write?.()}>
241
Transfer Tokens
242
</button>
243
);
244
}
245
```
246
247
## Advanced Authentication Patterns
248
249
### Full SIWE Implementation
250
251
```typescript
252
import {
253
RainbowKitAuthenticationProvider,
254
createAuthenticationAdapter,
255
AuthenticationStatus,
256
} from '@rainbow-me/rainbowkit';
257
import { SiweMessage } from 'siwe';
258
import { useState, useEffect } from 'react';
259
import { useAccount } from 'wagmi';
260
261
function AuthenticationWrapper({ children }) {
262
const [authenticationStatus, setAuthenticationStatus] = useState<AuthenticationStatus>('loading');
263
const { isConnected } = useAccount();
264
265
useEffect(() => {
266
const fetchStatus = async () => {
267
if (!isConnected) {
268
setAuthenticationStatus('unauthenticated');
269
return;
270
}
271
272
try {
273
const response = await fetch('/api/me');
274
setAuthenticationStatus(response.ok ? 'authenticated' : 'unauthenticated');
275
} catch {
276
setAuthenticationStatus('unauthenticated');
277
}
278
};
279
280
fetchStatus();
281
}, [isConnected]);
282
283
const authenticationAdapter = createAuthenticationAdapter({
284
getNonce: async () => {
285
const response = await fetch('/api/nonce');
286
return await response.text();
287
},
288
289
createMessage: ({ nonce, address, chainId }) => {
290
return new SiweMessage({
291
domain: window.location.host,
292
address,
293
statement: 'Sign in with Ethereum to access protected features.',
294
uri: window.location.origin,
295
version: '1',
296
chainId,
297
nonce,
298
expirationTime: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24 hours
299
});
300
},
301
302
verify: async ({ message, signature }) => {
303
const response = await fetch('/api/verify', {
304
method: 'POST',
305
headers: { 'Content-Type': 'application/json' },
306
body: JSON.stringify({
307
message: message.prepareMessage(),
308
signature,
309
}),
310
});
311
312
const success = response.ok;
313
setAuthenticationStatus(success ? 'authenticated' : 'unauthenticated');
314
return success;
315
},
316
317
signOut: async () => {
318
await fetch('/api/logout', { method: 'POST' });
319
setAuthenticationStatus('unauthenticated');
320
},
321
});
322
323
return (
324
<RainbowKitAuthenticationProvider
325
adapter={authenticationAdapter}
326
status={authenticationStatus}
327
>
328
{children}
329
</RainbowKitAuthenticationProvider>
330
);
331
}
332
```
333
334
### Server-Side SIWE Verification
335
336
```typescript
337
// API route: /api/verify
338
import { SiweMessage } from 'siwe';
339
import { NextApiRequest, NextApiResponse } from 'next';
340
341
export default async function handler(
342
req: NextApiRequest,
343
res: NextApiResponse
344
) {
345
const { method } = req;
346
347
switch (method) {
348
case 'POST':
349
try {
350
const { message, signature } = req.body;
351
const siweMessage = new SiweMessage(message);
352
353
const fields = await siweMessage.verify({ signature });
354
355
if (fields.success) {
356
// Store authentication state (session, JWT, etc.)
357
req.session.siwe = fields.data;
358
req.session.save();
359
res.json({ success: true });
360
} else {
361
res.status(422).json({ message: 'Invalid signature.' });
362
}
363
} catch (error) {
364
res.status(400).json({ message: error.message });
365
}
366
break;
367
default:
368
res.setHeader('Allow', ['POST']);
369
res.status(405).end(`Method ${method} Not Allowed`);
370
}
371
}
372
```
373
374
### Conditional Rendering Based on Authentication
375
376
```typescript
377
import { useAccount } from 'wagmi';
378
import { ConnectButton } from '@rainbow-me/rainbowkit';
379
380
function ProtectedContent() {
381
const { isConnected } = useAccount();
382
383
// This would typically use a custom hook to check authentication status
384
// For example: const { isAuthenticated } = useAuthentication();
385
386
if (!isConnected) {
387
return (
388
<div>
389
<h2>Connect Your Wallet</h2>
390
<p>Please connect your wallet to access this content.</p>
391
<ConnectButton />
392
</div>
393
);
394
}
395
396
// In a real app, you'd check authentication status here
397
return (
398
<div>
399
<h2>Protected Content</h2>
400
<p>This content is only visible to authenticated users.</p>
401
{/* Protected content here */}
402
</div>
403
);
404
}
405
```
406
407
### Custom Authentication Status Hook
408
409
```typescript
410
import { useState, useEffect } from 'react';
411
import { useAccount } from 'wagmi';
412
413
export function useAuthenticationStatus() {
414
const [isAuthenticated, setIsAuthenticated] = useState(false);
415
const [isLoading, setIsLoading] = useState(true);
416
const { isConnected, address } = useAccount();
417
418
useEffect(() => {
419
const checkAuthStatus = async () => {
420
if (!isConnected || !address) {
421
setIsAuthenticated(false);
422
setIsLoading(false);
423
return;
424
}
425
426
try {
427
const response = await fetch('/api/me');
428
setIsAuthenticated(response.ok);
429
} catch {
430
setIsAuthenticated(false);
431
} finally {
432
setIsLoading(false);
433
}
434
};
435
436
checkAuthStatus();
437
}, [isConnected, address]);
438
439
const signOut = async () => {
440
try {
441
await fetch('/api/logout', { method: 'POST' });
442
setIsAuthenticated(false);
443
} catch (error) {
444
console.error('Sign out failed:', error);
445
}
446
};
447
448
return {
449
isAuthenticated,
450
isLoading,
451
signOut,
452
};
453
}
454
```