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.
| Ruta | Qué hace |
|---|---|
GET /blockauth/login | Crea un state aleatorio, lo guarda en la sesión y redirige al login de Block-Auth |
GET /blockauth/callback | Comprueba el state, canjea el code y crea tu sesión |
GET /api/me | Devuelve el usuario con sesión, o 401 |
POST /blockauth/logout | Cierra 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)
El state protege tu callback frente a CSRF: compruébalo siempre antes de
canjear el código. El enlace de login debe abrirse en menos de 5 minutos, y el
código es válido durante 60 segundos y una sola vez. Todos los parámetros
están en Login de usuarios.
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.
- 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). - Añade la ruta. Comprueba la firma (cabeceras
X-BlockAuth-TimestampyX-BlockAuth-Signature), comprueba que laapiKeyes la tuya e ignora losjtirepetidos:
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.

