Connexion Ethereum sécurisée avec Next.js : guide pas à pas avec l'AppKit de WalletConnect
Thomas CosiallsDes méthodes d'authentification sûres et agréables sont indispensables pour faire entrer davantage d'utilisateurs dans le web3. Sign In With Ethereum (SIWE) s'est imposé comme une solution puissante : elle permet aux utilisateurs de contrôler leur identité numérique via leur compte Ethereum. Le nouvel AppKit de WalletConnect rend l'intégration du social login, des solutions d'on-ramp et de SIWE plus simple que jamais. Cet article vous guide pas à pas dans l'implémentation de SIWE dans une application Next.js à l'aide de la fonctionnalité « One Click Auth » de l'AppKit, en insistant sur la sécurisation des routes d'API avec des jetons JWT.
Comprendre SIWE et l'AppKit
Sign In With Ethereum (SIWE) est une méthode d'authentification standardisée (EIP-4361) qui permet à un utilisateur de prouver la propriété de son adresse Ethereum au moyen d'une signature cryptographique. L'AppKit de WalletConnect simplifie ce processus en offrant une intégration prête à l'emploi aux développeurs.
La fonctionnalité « One Click Auth » de l'AppKit représente une vraie avancée en matière d'expérience utilisateur : elle permet de connecter son wallet et de signer un message SIWE en un seul clic. Ce parcours fluide renforce la sécurité tout en améliorant l'expérience globale des applications décentralisées (dApp).
Mise en place
Pour commencer, installez correctement l'AppKit de WalletConnect en suivant ce tutoriel. Nous pouvons ensuite configurer la fonctionnalité « One Click Auth » ; lancez cette commande dans votre projet Next.js :
yarn add @web3modal/siwe next-auth
Configuration du client SIWE
Créons le client SIWE. Créez un fichier app/config/siwe.ts et collez-y ce code :
import { getCsrfToken, signIn, signOut, getSession } from 'next-auth/react'
import type { SIWEVerifyMessageArgs, SIWECreateMessageArgs, SIWESession } from '@web3modal/siwe'
import { createSIWEConfig, formatMessage } from '@web3modal/siwe'
import { mainnet, sepolia } from 'viem/chains'
export const siweConfig = createSIWEConfig({
getMessageParams: async () => ({
domain: typeof window !== 'undefined' ? window.location.host : '',
uri: typeof window !== 'undefined' ? window.location.origin : '',
chains: [mainnet.id, sepolia.id],
statement: 'Please sign with your account'
}),
createMessage: ({ address, ...args }: SIWECreateMessageArgs) => formatMessage(args, address),
getNonce: async () => {
const nonce = await getCsrfToken()
if (!nonce) {
throw new Error('Failed to get nonce!')
}
return nonce
},
getSession: async () => {
const session = await getSession()
if (!session) {
throw new Error('Failed to get session!')
}
const { address, chainId } = session as unknown as SIWESession
return { address, chainId }
},
verifyMessage: async ({ message, signature }: SIWEVerifyMessageArgs) => {
try {
const success = await signIn('credentials', {
message,
redirect: false,
signature,
callbackUrl: '/protected'
})
return Boolean(success?.ok)
} catch (error) {
return false
}
},
signOut: async () => {
try {
await signOut({
redirect: false
})
return true
} catch (error) {
return false
}
}
})Cette configuration définit les paramètres de base des messages SIWE et gère la génération du nonce. Ajoutez-la à votre objet createWeb3Modal, et c'est terminé !
import { siweConfig } from '@/config/siwe'
createWeb3Modal({
//...
siweConfig
})Après connexion, les utilisateurs seront invités à signer un message avec leur wallet :

Mise en place de la route d'API
Créons maintenant une route d'API pour gérer l'authentification. Créez le fichier app/api/auth/[...nextauth]/route.ts. N'oubliez pas de définir NEXTAUTH_SECRET et NEXT_PUBLIC_PROJECT_ID dans votre fichier .env.local. Je recommande d'utiliser un générateur de mots de passe pour votre phrase secrète.
import NextAuth from 'next-auth'
import credentialsProvider from 'next-auth/providers/credentials'
import {
type SIWESession,
verifySignature,
getChainIdFromMessage,
getAddressFromMessage,
} from '@web3modal/siwe'
declare module 'next-auth' {
interface Session extends SIWESession {
address: string
chainId: number
}
}
const nextAuthSecret = process.env.NEXTAUTH_SECRET
if (!nextAuthSecret) {
throw new Error('NEXTAUTH_SECRET is not set')
}
const projectId = process.env.NEXT_PUBLIC_PROJECT_ID
if (!projectId) {
throw new Error('NEXT_PUBLIC_PROJECT_ID is not set')
}
const providers = [
credentialsProvider({
name: 'Base',
credentials: {
message: {
label: 'Message',
type: 'text',
placeholder: '0x0',
},
signature: {
label: 'Signature',
type: 'text',
placeholder: '0x0',
},
},
async authorize(credentials) {
try {
if (!credentials?.message) {
throw new Error('SiweMessage is undefined')
}
const { message, signature } = credentials
const address = getAddressFromMessage(message)
const chainId = getChainIdFromMessage(message)
const isValid = await verifySignature({ address, message, signature, chainId, projectId })
if (isValid) {
return {
id: `${chainId}:${address}`,
}
}
return null
} catch (e) {
return null
}
},
}),
]
const handler = NextAuth({
// https://next-auth.js.org/configuration/providers/oauth
secret: nextAuthSecret,
providers,
session: {
strategy: 'jwt',
},
callbacks: {
session({ session, token }) {
if (!token.sub) {
return session
}
const [, chainId, address] = token.sub.split(':')
if (chainId && address) {
session.address = address
session.chainId = parseInt(chainId, 10)
}
return session
},
},
})
export { handler as GET, handler as POST }Cette mise en place crée un handler NextAuth qui utilise SIWE pour l'authentification : il vérifie la signature et crée une session (via un jeton JWT) en cas de succès.
Sécuriser les routes d'API avec un middleware
Maintenant que SIWE est en place, sécurisons nos routes d'API à l'aide des jetons JWT de next-auth. Nous pouvons utiliser getToken() dans un middleware Next.js pour vérifier la validité du jeton, récupérer l'adresse du wallet et la transmettre à la route d'API :
import { getToken } from 'next-auth/jwt'
import { NextResponse, type NextRequest } from 'next/server'
// List routes you want to protect here
export const config = {
matcher: ['/api/events/:path*', '/api/users/:path*'],
}
export async function middleware(req: NextRequest) {
//get wallet id from session
const token = await getToken({ req })
if (!token) {
return NextResponse.json({ success: false, message: 'Authentication failed' }, { status: 401 })
}
//get wallet id from session token
const [, chainId, address] = token.sub.split(':')
// set user wallet address in the request headers
const requestHeaders = new Headers(req.headers)
requestHeaders.set('wallet-address', address)
return NextResponse.next({
request: {
headers: requestHeaders,
},
})
}
Ce middleware protège automatiquement les routes indiquées : l'authentification devient obligatoire pour y accéder.
Conclusion
Implémenter Sign In With Ethereum (SIWE) avec l'AppKit de WalletConnect dans votre application Next.js vous donne une méthode d'authentification à la fois sûre et agréable. En vous appuyant sur les jetons JWT de next-auth pour sécuriser vos routes d'API, vous obtenez un système robuste qui protège les données de vos utilisateurs tout en offrant les avantages de l'authentification Web3.
Pensez à maintenir vos mécanismes d'authentification à jour et à suivre les bonnes pratiques de sécurité. Avec SIWE et une protection correcte de vos routes d'API, vous êtes bien parti pour construire des applications décentralisées sûres et engageantes. Bon code, et bienvenue dans le futur de l'authentification Web3 !

