VERISCO Shield Integrate
Read the signed-in user
After sign-in your application receives a signed statement of who the user is. Verify the signature, then trust what it says.
What arrives with each request#
| Name | Contains | |
|---|---|---|
| Cookie | verisco_session |
The signed assertion. Set by the gateway on your hostname. |
| Header | X-Verisco-Claims |
The same signed assertion, added by the gateway to every forwarded request. |
| Header | X-Verisco-User |
The user's identifier, as plain text. A convenience for logs. |
| Header | X-Verisco-Verified |
1 when the gateway validated the session. |
Verify before you trust
The cookie and the claims header carry the same RS256 token. Verify its signature against your tenant's public key and reject an expired one. Never act on the plain-text headers alone: a header is only as trustworthy as the network path it arrived on.
The claims#
{
"user": {
"id": "1042",
"email": "amina@example.com",
"roles": ["manager"]
},
"iat": 1790000000,
"exp": 1790028800
}| Claim | Meaning |
|---|---|
exp |
Expires at (epoch seconds) โ the backend MUST reject an expired assertion |
iat |
Issued at (epoch seconds) |
user.email |
Email address, when the tenant's schema has one |
user.id |
Stable identifier from the tenant's own user table |
user.roles |
List of role names, sourced from the tenant's role column |
user also carries the other columns of the user's row in your own table, except the password column. The token also carries iss and sub, and has no aud claim, so a verifier does not need to be given an audience.
Get your public key#
verisco-analyzer download-key --api-key <your analyzer key> --output verisco_public.pemOr without the CLI:
curl -fsS "https://your-verisco-host/api/v1/public-key?api_key=<your analyzer key>" -o verisco_public.pemVerify it#
const { verisco, protect } = require('@verisco/verisco-middleware');
// Mount before your routes. Populates req.user from the
// verisco_session cookie, verified with your RS256 public key.
app.use(verisco({ publicKeyFile: './config/verisco_public.pem' }));
// Require a signed-in user on a route:
app.get('/dashboard', protect(), (req, res) => res.json(req.user));import { verisco, protect } from '@verisco/verisco-middleware';
// Mount before your routes. Populates req.user from the
// verisco_session cookie, verified with your RS256 public key.
app.use(verisco({ publicKeyFile: './config/verisco_public.pem' }));
// Require a signed-in user on a route:
app.get('/dashboard', protect(), (req, res) => res.json(req.user));const verisco = require('@verisco/verisco-middleware');
// Register before your routes. Populates request.user from the
// verisco_session cookie, verified with your RS256 public key.
app.register(verisco.fastify, { publicKeyFile: './config/verisco_public.pem' });
// Require a signed-in user on a route:
app.get('/dashboard', { onRequest: [verisco.fastifyProtect] },
async (request) => request.user);import jwt
from fastapi import FastAPI, HTTPException, Request
app = FastAPI()
PUBLIC_KEY = open("./config/verisco_public.pem").read()
def current_user(request: Request) -> dict:
token = request.headers.get("X-Verisco-Claims")
if not token:
raise HTTPException(status_code=401)
try:
claims = jwt.decode(token, PUBLIC_KEY, algorithms=["RS256"])
except jwt.PyJWTError:
raise HTTPException(status_code=401)
return claims["user"]
@app.get("/dashboard")
def dashboard(request: Request):
return current_user(request)<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$publicKey = file_get_contents(__DIR__ . '/config/verisco_public.pem');
$token = $_SERVER['HTTP_X_VERISCO_CLAIMS'] ?? '';
try {
$claims = JWT::decode($token, new Key($publicKey, 'RS256'));
$user = $claims->user;
} catch (Throwable $e) {
http_response_code(401);
exit;
}func currentUser(r *http.Request, key *rsa.PublicKey) (map[string]any, error) {
token, err := jwt.Parse(r.Header.Get("X-Verisco-Claims"),
func(t *jwt.Token) (any, error) { return key, nil },
jwt.WithValidMethods([]string{"RS256"}))
if err != nil || !token.Valid {
return nil, errors.New("unauthorised")
}
claims := token.Claims.(jwt.MapClaims)
return claims["user"].(map[string]any), nil
}The Node middleware reads the cookie; the other examples read the header. Either is correct, because both carry the same token.
Middleware options#
| Option | Meaning |
|---|---|
publicKey |
The public key itself, as a PEM string. Use it when the key comes from an environment variable. |
publicKeyFile |
Path to your public key file. |
The package exports fastify, fastifyProtect, protect, verisco.
Requiring a signed-in user#
protect() redirects a request with no verified user to /verisco/login, with returnTo set to the page they asked for, so they land back on it after signing in.
Roles#
user.roles comes from the role column you mapped during onboarding. Map it to your own permissions in your application:
function requireRole(role) {
return (req, res, next) =>
req.user && req.user.roles.includes(role) ? next() : res.status(403).end();
}
app.get('/admin', protect(), requireRole('admin'), adminHandler);Key rotation#
When your tenant's signing key is rotated, download the new public key and deploy it. The token's header carries a kid, so a verifier that holds both keys can pick the right one during the changeover.
In SDK mode#
None of this applies. Your application is the sign-in client and the middleware sets req.user from its own session. See SDK mode.
Something missing or wrong on this page? Tell us and we will fix it.