Your backend
Add the routes the widget calls and receive webhooks. With the Block-Auth package for your server it's mostly configuration: it redeems the login code, keeps the session and verifies the webhooks for you. If you prefer, you can write it yourself.
How you integrate it
Add the backend routes
Install the Block-Auth package for your server:
npm install @block-auth.io/node express express-session
Create BlockAuthRoutes with your keys and the URL of your frontend, and mount it in your server:
import express from 'express'
import session from 'express-session'
import { BlockAuthRoutes } from '@block-auth.io/node'
import { blockauthRouter } from '@block-auth.io/node/express'
const { APP_URL, BLOCKAUTH_API_KEY, BLOCKAUTH_API_SECRET, SESSION_SECRET } = process.env
export const routes = new BlockAuthRoutes({
apiKey: BLOCKAUTH_API_KEY,
apiSecret: BLOCKAUTH_API_SECRET,
appUrl: APP_URL,
})
export const app = express()
app.use(
session({
secret: SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { httpOnly: true, sameSite: 'lax', secure: APP_URL.startsWith('https') },
}),
)
app.use(blockauthRouter(routes))
app.use(express.json())
app.listen(4000)
That's all. Your server now answers these routes under /auth/blockauth, the path the widget calls by default:
| Route | Does |
|---|---|
POST /code | Redeems the login code at Block-Auth, creates the session and returns { address, did, roles } |
GET /me | Returns the signed-in user, or 401 |
POST /logout | Closes the session in your app and in Block-Auth |
GET and POST /link | Links your existing users (only with the appUser option) |
POST /webhook | Receives the webhooks |
What the package already does for you:
- Trusts only Block-Auth. The user always comes from redeeming the code with your
apiSecret, and theredirectUrimust be onappUrl. If your frontend lives on several origins, passallowedOrigins(a list) instead. - Blocks forged requests. It only accepts
POSTrequests with a JSON body, so another site can't log your users in with a form. - Keeps the session short and clean. It renews the session ID on every login and ends it when the user's Block-Auth signature expires (
signedExpiredAt). To end it earlier, setmaxSessionSeconds(MaxSessionDurationin .NET). - Logs out everywhere.
POST /logoutalso closes the user's Block-Auth sessions for your app, and keeps the rest of your session data. - Verifies every webhook. Signature, time and
apiKey, before calling your code.
APP_URL is the origin of your frontend, the one in your validated
domain (for example https://your-app.com). The SDK hashes the apiSecret
itself: pass it as it comes from the Dashboard. In Node.js, mount
blockauthRouter before express.json(), so the webhook is verified
against the raw body. If your server runs behind a proxy that terminates
https, add app.set('trust proxy', 1) or Express won't send the secure
cookie.
If your routes must live under another path, set it in basePath and the same path as authEndpoint in the widget. To turn off a route you don't use, pass routes: { link: false } (in .NET, add it to DisabledRoutes). Option names are snake_case in Python and PascalCase in .NET.
Use the session in your routes
From now on, the session is the source of truth. Read the user before doing anything that needs a signed-in user or a role. You get null when there is no session, it expired or it was closed by a webhook:
import { expressSessionStore } from '@block-auth.io/node/express'
const currentUser = (req) => routes.currentUser({ session: expressSessionStore(req) })
const requireRole = (slug) => async (req, res, next) => {
const user = await currentUser(req)
if (!user) return res.status(401).end()
if (!user.roles.some((role) => role.slug === slug)) return res.status(403).end()
next()
}
app.delete('/api/orders/:id', requireRole('acme-x7k2p9'), deleteOrder)
The user has address, did, roles ({ name, slug }), externalId (your user ID, if linked), loggedInAt, expiresAt (Unix seconds) and data. In .NET the roles are also role claims with the role slug. Use the role slug, not the name: see Roles.
To keep your own data in the session, or to refuse a login, add onLogin(user): return an object to save it as data, or false to reject the user (they are also signed out of Block-Auth). In .NET, AuthorizeLogin decides and OnLogin returns the data. onLogout(user) runs when the user logs out.
Receive webhooks
Block-Auth calls your backend when a user signs out of your app from Block-Auth (signout) or unlinks your app (account.unlinked), so you can close their session too.
This is how a sign-out made in Block-Auth (Identity, the extension or another device) reaches your page:
- 1Block-AuthThe user signs out
- 2Block-Auth → Your backendPOST /webhook · signout
- 3Your backendSaves the sign-out
- 4Your frontend → Your backendGET /me on focus or every 60 s
- 5Your backend → Your frontend401
- 6Your frontendShows the user signed out
Block-Auth widget
Your session
Hosted by Block-Auth
- 1The user signs out
- 2POST /webhook · signout
- 3Saves the sign-out
- 4GET /me on focus or every 60 s
- 5401
- 6Shows the user signed out
- In the Dashboard, open your application's Authentication settings. In the third URL field, shown as "When user fails to log in", type the path
/auth/blockauth/webhook(your domain is already filled in).
- Tell the package how to remember a sign-out and how to check it.
POST /webhookis already there: it verifies the request and callsonSignoutoronUnlinked. ThenisRevokedcloses every session that started before that sign-out:
saveSignout, lastSignout and removeLink are yours: keep them in your database or a shared cache such as Redis, so every server instance sees them. address arrives in lowercase and iat and loggedInAt are Unix seconds.
isRevokedruns on everyGET /meand every time you read the user. The widget asks/mewhen the tab gets focus and every minute, so the page signs out by itself.- To reject a repeated delivery, add
isReplay(jti)and returntrueif you already saw thatjti.

