Skip to content

Authentication (SIWE)

Example

Demo
pnpx gitpick ithacaxyz/porto/tree/main/examples/authentication
Source

Steps

Connect Account

Follow the Onboard & Discover Accounts guide to get this set up.

Setup API & add /siwe/nonce

Next, we will set up our API endpoints for our authentication flow.

Sign in with Ethereum requires a nonce to be generated by the server to prevent replay attacks. You will need to set up a API endpoint to return a nonce. For example, using Hono and Viem.

import { Hono } from 'hono'
import { Porto } from 'porto'
import { generateSiweNonce } from 'viem/siwe'
 
const app = new Hono().basePath('/api')
const porto = Porto.create()
 
app.post('/siwe/nonce', (c) => c.json({ nonce: generateSiweNonce() }))
 
app.post('/siwe/verify', async (c) => { /* ... */ })
 
app.post('/siwe/logout', async (c) => { /* ... */ })
 
app.get('/me', async (c) => { /* ... */ })
 
export default app

Add /siwe/verify

We will now implement the /api/siwe endpoint so that we can authenticate our user provided the siwe response from the previous step is valid.

import { Hono } from 'hono'
import { setCookie } from 'hono/cookie'
import * as jwt from 'hono/jwt'
import { Porto, ServerActions } from 'porto'
import { ServerClient } from 'porto/viem'
import { hashMessage } from 'viem'
import { generateSiweNonce, parseSiweMessage } from 'viem/siwe'
 
const app = new Hono().basePath('/api')
const porto = Porto.create()
 
app.post('/siwe/nonce', (c) => c.text(generateSiweNonce()))
 
app.post('/siwe/verify', async (c) => { 
  const { message, signature } = await c.req.json() 
  const { address, chainId, nonce } = parseSiweMessage(message) 
 
  // Verify the signature. 
  const client = ServerClient.fromPorto(porto, { chainId }) 
  const valid = ServerActions.verifySignature(client, { 
    address: address!, 
    digest: hashMessage(message), 
    signature, 
  }) 
 
  // If the signature is invalid, we cannot authenticate the user. 
  if (!valid) return c.json({ error: 'Invalid signature' }, 401) 
 
  // Issue a JWT for the user in a HTTP-only cookie. 
  const token = await jwt.sign({ sub: address }, c.env.JWT_SECRET) 
  setCookie(c, 'auth', token, { 
    httpOnly: true, 
    secure: true, 
  }) 
 
  // If the signature is valid, we can authenticate the user. 
  return c.json({ message: 'Authenticated' }) 
})
 
app.post('/siwe/logout', async (c) => { /* ... */ })
 
app.get('/me', async (c) => { /* ... */ })
 
export default app

Done

Now that you have set up your /api/siwe endpoints and have your server up and running, you can pass the signInWithEthereum.authUrl capability to the connect call to have control over the authentication URL.

import { useConnectors } from 'wagmi'
import { Hooks } from 'porto/wagmi'
 
export function Example() {
  const [connector] = useConnectors()
  const { connect } = Hooks.useConnect()
 
  return (
    <button 
      onClick={() => 
        connect({
          connector, 
          signInWithEthereum: { 
            authUrl: '/api/siwe', 
          }, 
        })
      }
    >
      Sign in
    </button>
  )
}

Bonus: Add /me (authenticated route)

We can now add an authenticated routes to our app that can only be accessed if the user is authenticated.

In this example, we are using the hono/jwt middleware to check if the user is authenticated (they hold a valid auth cookie).

import { Hono } from 'hono'
import { jwt } from 'hono/jwt'
import { Porto } from 'porto'
 
const app = new Hono().basePath('/api')
const porto = Porto.create()
 
app.get(
  '/me', 
  jwt({ cookie: 'auth' }), 
  async (c) => {
    return c.json({ user: 'John Doe' }) 
  }
)

Bonus: Add /siwe/logout

We can also log out a user by deleting the auth cookie with hono/cookie's deleteCookie function.

import { Hono } from 'hono'
import { deleteCookie } from 'hono/cookie'
import { jwt } from 'hono/jwt'
import { Porto } from 'porto'
 
const app = new Hono().basePath('/api')
const porto = Porto.create()
 
app.post(
  '/logout', 
  jwt({ cookie: 'auth' }),
  async (c) => {
    deleteCookie(c, 'auth') 
    return c.text('So long, friend.') 
  }
)