Skip to content

Instantly share code, notes, and snippets.

@tatsuyasusukida
Created August 30, 2022 01:59
Show Gist options
  • Save tatsuyasusukida/1293733300e689bd5556f4619626130f to your computer and use it in GitHub Desktop.
Save tatsuyasusukida/1293733300e689bd5556f4619626130f to your computer and use it in GitHub Desktop.
Web3 login practice
COOKIE_NAME="web3login_session"
COOKIE_PASSWORD="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
import crypto from "crypto";
import { NextApiHandler } from "next";
import { withSession } from "../../lib/with-session";
const apiMessage: NextApiHandler = async (req, res) => {
const {address} = req.body
const nonce = crypto.randomUUID()
const issuedAt = new Date().toISOString()
const token = {address, nonce, issuedAt}
const message = [
`Welcome to XxxxXxx!`,
``,
`Click to sign in and accept the XxxxXxx Terms of Service: https://xxxxxxx.io/tos`,
``,
`This request will not trigger a blockchain transaction or cost any gas fees.`,
``,
`Your authentication status will reset after 24 hours.`,
``,
`Wallet address:`,
`${token.address}`,
``,
`Nonce:`,
`${token.nonce}`,
].join('\n')
req.session.token = token
await req.session.save()
res.send({message})
}
export default withSession(apiMessage)
import crypto from "crypto";
import { ethers } from "ethers";
import { NextApiHandler } from "next";
import { withSession } from "../../lib/with-session";
const apiSignin: NextApiHandler = async (req, res) => {
const {token} = req.session
if (!token) {
res.send({ok: false})
return
}
const {message, signature} = req.body
const lines = message.split('\n')
const nonce = lines[lines.length - 1]
const digest = ethers.utils.hashMessage(message)
const expected = ethers.utils.recoverAddress(digest, signature)
const issuedAt = new Date(token.issuedAt).getTime()
const ok = token.address === expected
&& nonce === token.nonce
&& Date.now() < issuedAt + 15 * 60 * 1000
if (ok) {
req.session.address = token.address
delete req.session.token
await req.session.save()
}
res.send({ok})
}
export default withSession(apiSignin)
import { ethers } from "ethers";
import { NextPage } from "next";
import { useState } from "react";
async function post(url: string, body: object) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: JSON.stringify(body),
})
return await response.json()
}
declare global {
interface Window {
ethereum: ethers.providers.ExternalProvider
}
}
const Signin: NextPage = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false)
const onClick = async () => {
const provider = new ethers.providers.Web3Provider(window.ethereum)
await provider.send('eth_requestAccounts', [])
const signer = provider.getSigner()
const address = await signer.getAddress()
const {message} = await post('/api/message', {address})
const signature = await signer.signMessage(message)
const {ok} = await post('/api/signin', {message, signature})
setIsAuthenticated(ok)
}
return (
<main>
<h1>Web3 Login </h1>
<button onClick={onClick}>Signin</button>
{isAuthenticated && (
<p>Sign in completed</p>
)}
</main>
)
}
export default Signin
import { withIronSessionApiRoute } from "iron-session/next";
import { NextApiHandler } from "next";
declare module "iron-session" {
interface IronSessionData {
token?: {
address: string
nonce: string
issuedAt: string
}
address?: string
}
}
export function withSession(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, {
cookieName: process.env.COOKIE_NAME as string,
password: process.env.COOKIE_PASSWORD as string,
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment