Login

Añade las rutas de login a tu servidor y los enlaces a tus páginas.

Añade las rutas de login

Añade estas cuatro rutas. La URL del callback (/blockauth/callback) tiene que estar en tu dominio validado.

RutaQué hace
GET /blockauth/loginCrea un state aleatorio, lo guarda en la sesión y redirige al login de Block-Auth
GET /blockauth/callbackComprueba el state, canjea el code y crea tu sesión
GET /api/meDevuelve el usuario con sesión, o 401
POST /blockauth/logoutCierra las sesiones de Block-Auth y la tuya
import crypto from 'node:crypto'
import express from 'express'
import session from 'express-session'
import { AuthCodeClient } from '@block-auth.io/node'

const { APP_URL, BLOCKAUTH_API_KEY, BLOCKAUTH_API_SECRET, SESSION_SECRET } = process.env

export const blockauth = new AuthCodeClient(BLOCKAUTH_API_KEY, BLOCKAUTH_API_SECRET)

const sessionUser = (user) => ({
  address: user.address,
  roles: user.roles,
  expiresAt: Date.parse(`${user.signedExpiredAt.replace(' ', 'T')}Z`),
})

export const currentUser = (req) => {
  const user = req.session.user
  if (user && Date.now() < user.expiresAt) return user
  delete req.session.user
  return null
}

export const app = express()

app.use(
  session({
    secret: SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: { httpOnly: true, sameSite: 'lax', secure: APP_URL.startsWith('https') },
  }),
)

const CALLBACK_URL = `${APP_URL}/blockauth/callback`

app.get('/blockauth/login', async (req, res) => {
  const state = crypto.randomBytes(16).toString('hex')
  req.session.oauthState = state
  try {
    const { url } = await blockauth.authorize(CALLBACK_URL, state)
    res.redirect(url)
  } catch {
    res.redirect('/login?error=blockauth')
  }
})

app.get('/blockauth/callback', async (req, res) => {
  const { code, state } = req.query
  const expected = req.session.oauthState
  delete req.session.oauthState
  if (typeof code !== 'string' || !expected || state !== expected) {
    return res.redirect('/login?error=invalid_state')
  }
  try {
    const user = await blockauth.exchangeCode(code, CALLBACK_URL)
    req.session.regenerate((err) => {
      if (err) return res.redirect('/login?error=session')
      req.session.user = sessionUser(user)
      res.redirect('/')
    })
  } catch {
    res.redirect('/login?error=blockauth')
  }
})

app.get('/api/me', (req, res) => {
  const user = currentUser(req)
  if (!user) return res.status(401).json({ error: 'unauthenticated' })
  res.json({ address: user.address, roles: user.roles })
})

app.post('/blockauth/logout', async (req, res) => {
  const user = req.session.user
  if (user) await blockauth.signOut(user.address).catch(() => {})
  req.session.destroy(() => res.redirect('/'))
})

app.listen(4000)

Tu sesión termina cuando termina la firma del usuario en Block-Auth: signedExpiredAt, en UTC con el formato YYYY-MM-DD HH:mm:ss. Por eso la sesión guarda ese momento y cada ruta lo comprueba antes de fiarse del usuario (cuánto dura una sesión).

Añade los enlaces a tus páginas

En tus plantillas, el login es un enlace normal y el logout un formulario. Para saber quién tiene sesión, lee tu sesión (o llama a GET /api/me desde tus scripts).

HTML

<a href="/blockauth/login">Sign in with Block-Auth</a>

<form method="post" action="/blockauth/logout">
  <button type="submit">Sign out</button>
</form>

Recibe los webhooks

Block-Auth llama a tu backend cuando un usuario cierra la sesión de tu app desde Block-Auth (signout) o desvincula tu app (account.unlinked), para que también puedas cerrar su sesión.

  1. En el Dashboard, abre tu aplicación y ve a Autenticación. En el tercer campo de URL, que aparece como «Cuando falla el inicio de sesión», escribe la ruta /blockauth/webhook (tu dominio ya viene puesto).
  2. Añade la ruta. Comprueba la firma (cabeceras X-BlockAuth-Timestamp y X-BlockAuth-Signature), comprueba que la apiKey es la tuya e ignora los jti repetidos:

Body

{
  "event": "account.unlinked",
  "apiKey": "your_api_key",
  "address": "0x8f2a…c41d",
  "externalId": "42",
  "iat": 1762790400,
  "jti": "5c1e2b7a-…"
}
const seen = new Map()

app.post('/blockauth/webhook', express.raw({ type: '*/*' }), (req, res) => {
  let event
  try {
    event = blockauth.verifyWebhook(req.body, req.get('X-BlockAuth-Timestamp'), req.get('X-BlockAuth-Signature'))
  } catch {
    return res.status(401).end()
  }
  if (event.apiKey !== BLOCKAUTH_API_KEY || seen.has(event.jti)) return res.status(409).end()
  seen.set(event.jti, Date.now())

  endSessionsOf(event.address)
  if (event.event === 'account.unlinked') markUnlinked(event.externalId)
  res.json({ ok: true })
})

endSessionsOf, markUnlinked y alreadyProcessed son tuyas: dependen de cómo guarda tu app las sesiones y los usuarios.