Skip to content

Commit

Permalink
feat: viem/siwe migration (#2219)
Browse files Browse the repository at this point in the history
* chore: remove siwe, ethers

* feat: viem/siwe partial migration

* fix: skip message json stringify in signin api call

* fix: remove address addition

* chore: docs updates

* chore: changeset

chore: changeset

chore: changeset tweaks

chore: changeset tweaks
  • Loading branch information
DanielSinclair authored Oct 15, 2024
1 parent 20b1071 commit f02bced
Show file tree
Hide file tree
Showing 25 changed files with 398 additions and 617 deletions.
183 changes: 183 additions & 0 deletions .changeset/small-ladybugs-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
---
"@rainbow-me/rainbowkit": minor
---

The Authentication API now supports ERC-1271 and ERC-6492 for smart contract signature verification to enable Sign-in with Ethereum for Smart Contract Wallets, including Coinbase Smart Wallet and Argent.

We have also deprecated the `siwe` and `ethers` peer dependencies in favor of `viem/siwe` to make RainbowKit even more seamless.

No changes are necessary for dApps that don't rely on the Authentication API.

Follow the appropriate steps below to migrate.

** NextAuth Authentication **

1. Remove `siwe` and `ethers`

```bash
npm uninstall siwe ethers
```

2. Upgrade RainbowKit, `rainbowkit-siwe-next-auth`, and `viem`

```bash
npm i @rainbow-me/rainbowkit@^2.2.0 rainbow-me/rainbowkit-siwe-next-auth@^0.5.0 viem@^2.12.0
```

3. Create a Public Client

This allows `viem` to verify smart contract signatures.

```diff
const config = getDefaultConfig({
/* your config */
});
+ const publicClient = config.getClient().extend(publicActions);
```

4. Adjust your `authorize` implementation in `/api/auth/[...nextauth].ts`

```diff
- import { SiweMessage } from 'siwe';
+ import {
+ type SiweMessage,
+ parseSiweMessage,
+ validateSiweMessage,
+ } from 'viem/siwe';

export function getAuthOptions(req: IncomingMessage): NextAuthOptions {
const providers = [
CredentialsProvider({
async authorize(credentials: any) {

- const siwe = new SiweMessage(
- JSON.parse(credentials?.message || '{}'),
- );
+ const siweMessage = parseSiweMessage(
+ credentials?.message,
+ ) as SiweMessage;

+ if (!validateSiweMessage({
+ address: siweMessage?.address,
+ message: siweMessage,
+ })) {
+ return null;
+ }

/* ... */

- await siwe.verify({ signature: credentials?.signature || '' });
+ const valid = await publicClient.verifyMessage({
+ address: siweMessage?.address,
+ message: credentials?.message,
+ signature: credentials?.signature,
+ });

+ if (!valid) {
+ return null;
+ }
},
/* ... */
})
]
}
```

Reference the [with-next-siwe-next-auth](https://github.com/rainbow-me/rainbowkit/tree/main/examples/with-next-siwe-next-auth) example for more guidance.

** Custom Authentication **

1. Remove `siwe` and `ethers`

```bash
npm uninstall siwe ethers
```

2. Upgrade RainbowKit and `viem`

```bash
npm i @rainbow-me/rainbowkit@^2.2.0 viem@^2.12.0
```

3. Create a Public Client

This allows `viem` to verify smart contract signatures.

```diff
const config = getDefaultConfig({
/* your config */
});

+ const publicClient = config.getClient().extend(publicActions);
```

4. Adjust your `createAuthenticationAdapter` implementation

```diff
- import { SiweMessage } from 'siwe';
+ import { createSiweMessage } from 'viem/siwe';

createAuthenticationAdapter({
getNonce: async () => {
const response = await fetch('/api/nonce');
return await response.text();
},

createMessage: ({ nonce, address, chainId }) => {
- return new SiweMessage({
+ return createSiweMessage({
domain: window.location.host,
address,
statement: 'Sign in with Ethereum to the app.',
uri: window.location.origin,
version: '1',
chainId,
nonce,
});
},

- getMessageBody: ({ message }) => {
- return message.prepareMessage();
- },

/* ... */
})
```

5. Adopt `generateSiweNonce`

```diff
- import { generateNonce } from 'siwe';
+ import { generateSiweNonce } from 'viem/siwe';

- req.session.nonce = generateNonce();
+ req.session.nonce = generateSiweNonce();
```

6. Adopt `parseSiweMessage` and `verifyMessage` if your Verify handler

```diff
- import { SiweMessage } from 'siwe';
+ import { parseSiweMessage, type SiweMessage } from 'viem/siwe';

const { message, signature } = req.body;
- const siweMessage = new SiweMessage(message);
- const { success, error, data } = await siweMessage.verify({
- signature,
- });
+ const siweMessage = parseSiweMessage(message) as SiweMessage;
+ const success = await publicClient.verifyMessage({
+ address: siweMessage.address,
+ message,
+ signature,
+ });

- if (!success) throw error;
+ if (!success) throw new Error('Invalid signature.');

- if (data.nonce !== req.session.nonce)
+ if (siweMessage.nonce !== req.session.nonce)
+ return res.status(422).json({ message: 'Invalid nonce.' });
```

Reference the [with-next-siwe-iron-session](https://github.com/rainbow-me/rainbowkit/tree/main/examples/with-next-siwe-iron-session) example for more guidance.
82 changes: 82 additions & 0 deletions .changeset/witty-knives-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
"@rainbow-me/rainbowkit-siwe-next-auth": minor
---

The Authentication API now supports ERC-1271 and ERC-6492 for smart contract signature verification to enable Sign-in with Ethereum for Smart Contract Wallets.

We have also deprecated the `siwe` and `ethers` peer dependencies in favor of `viem/siwe`.

Follow the appropriate steps below to migrate.

1. Remove `siwe` and `ethers`

```bash
npm uninstall siwe ethers
```

2. Upgrade RainbowKit, `rainbowkit-siwe-next-auth`, and `viem`

```bash
npm i @rainbow-me/rainbowkit@^2.2.0 rainbow-me/rainbowkit-siwe-next-auth@^0.5.0 viem@^2.12.0
```

3. Create a Public Client

This allows `viem` to verify smart contract signatures.

```diff
const config = getDefaultConfig({
/* your config */
});
+ const publicClient = config.getClient().extend(publicActions);
```

4. Adjust your `authorize` implementation in `/api/auth/[...nextauth].ts`

```diff
- import { SiweMessage } from 'siwe';
+ import {
+ type SiweMessage,
+ parseSiweMessage,
+ validateSiweMessage,
+ } from 'viem/siwe';

export function getAuthOptions(req: IncomingMessage): NextAuthOptions {
const providers = [
CredentialsProvider({
async authorize(credentials: any) {

- const siwe = new SiweMessage(
- JSON.parse(credentials?.message || '{}'),
- );
+ const siweMessage = parseSiweMessage(
+ credentials?.message,
+ ) as SiweMessage;

+ if (!validateSiweMessage({
+ address: siweMessage?.address,
+ message: siweMessage,
+ })) {
+ return null;
+ }

/* ... */

- await siwe.verify({ signature: credentials?.signature || '' });
+ const valid = await publicClient.verifyMessage({
+ address: siweMessage?.address,
+ message: credentials?.message,
+ signature: credentials?.signature,
+ });

+ if (!valid) {
+ return null;
+ }
},
/* ... */
})
]
}
```

Reference the [with-next-siwe-next-auth](https://github.com/rainbow-me/rainbowkit/tree/main/examples/with-next-siwe-next-auth) example for more guidance.
2 changes: 0 additions & 2 deletions examples/with-next-siwe-iron-session/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
},
"dependencies": {
"@rainbow-me/rainbowkit": "workspace:*",
"ethers": "^5.6.8",
"iron-session": "^6.3.1",
"next": "^14.2.10",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"siwe": "^2.1.4",
"viem": "2.17.0",
"wagmi": "^2.12.17",
"@tanstack/react-query": "^5.55.3"
Expand Down
8 changes: 2 additions & 6 deletions examples/with-next-siwe-iron-session/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
RainbowKitAuthenticationProvider,
AuthenticationStatus,
} from '@rainbow-me/rainbowkit';
import { SiweMessage } from 'siwe';
import { createSiweMessage } from 'viem/siwe';

import { config } from '../wagmi';

Expand Down Expand Up @@ -60,7 +60,7 @@ export default function App({ Component, pageProps }: AppProps) {
},

createMessage: ({ nonce, address, chainId }) => {
return new SiweMessage({
return createSiweMessage({
domain: window.location.host,
address,
statement: 'Sign in with Ethereum to the app.',
Expand All @@ -71,10 +71,6 @@ export default function App({ Component, pageProps }: AppProps) {
});
},

getMessageBody: ({ message }) => {
return message.prepareMessage();
},

verify: async ({ message, signature }) => {
verifyingRef.current = true;

Expand Down
5 changes: 3 additions & 2 deletions examples/with-next-siwe-iron-session/src/pages/api/nonce.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { withIronSessionApiRoute } from 'iron-session/next';
import { NextApiRequest, NextApiResponse } from 'next';
import { generateNonce } from 'siwe';
import { generateSiweNonce } from 'viem/siwe';

import { ironOptions } from '../../../lib/iron';

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'GET':
req.session.nonce = generateNonce();
req.session.nonce = generateSiweNonce();
await req.session.save();
res.setHeader('Content-Type', 'text/plain');
res.send(req.session.nonce);
Expand Down
17 changes: 11 additions & 6 deletions examples/with-next-siwe-iron-session/src/pages/api/verify.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { withIronSessionApiRoute } from 'iron-session/next';
import { NextApiRequest, NextApiResponse } from 'next';
import { SiweMessage } from 'siwe';
import { parseSiweMessage, type SiweMessage } from 'viem/siwe';

import { ironOptions } from '../../../lib/iron';
import { publicClient } from '../../wagmi';

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'POST':
try {
const { message, signature } = req.body;
const siweMessage = new SiweMessage(message);
const { success, error, data } = await siweMessage.verify({
const siweMessage = parseSiweMessage(message) as SiweMessage;

const success = await publicClient.verifyMessage({
address: siweMessage.address,
message,
signature,
});

if (!success) throw error;
if (!success) throw new Error('Invalid signature.');

if (data.nonce !== req.session.nonce)
if (siweMessage.nonce !== req.session.nonce)
return res.status(422).json({ message: 'Invalid nonce.' });

req.session.siwe = data;
req.session.siwe = siweMessage;
await req.session.save();
res.json({ ok: true });
} catch (_error) {
Expand Down
3 changes: 3 additions & 0 deletions examples/with-next-siwe-iron-session/src/wagmi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getDefaultConfig } from '@rainbow-me/rainbowkit';
import { publicActions } from 'viem';
import {
arbitrum,
base,
Expand All @@ -21,3 +22,5 @@ export const config = getDefaultConfig({
],
ssr: true,
});

export const publicClient = config.getClient().extend(publicActions);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'iron-session';
import { SiweMessage } from 'siwe';
import type { SiweMessage } from 'viem/siwe';

declare module 'iron-session' {
interface IronSessionData {
Expand Down
2 changes: 0 additions & 2 deletions examples/with-next-siwe-next-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
"dependencies": {
"@rainbow-me/rainbowkit": "workspace:*",
"@rainbow-me/rainbowkit-siwe-next-auth": "workspace:*",
"ethers": "^5.6.8",
"next": "^14.2.10",
"next-auth": "4.24.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"siwe": "^2.1.4",
"viem": "2.17.0",
"wagmi": "^2.12.17",
"@tanstack/react-query": "^5.55.3"
Expand Down
Loading

0 comments on commit f02bced

Please sign in to comment.