Skip to Content
GuidesExpress.js Middleware

Express.js Middleware

Build Express middleware that verifies Solidus credentials and protects API routes. This guide covers JWT verification, DID resolution, and credential validation.

Prerequisites

  • Node.js 18+
  • An existing Express application (or create a new one)

1. Install Dependencies

npm install @solidus-network/sdk @solidus-network/auth @solidus-network/jwt express npm install -D @types/express typescript

2. Create the SDK Instance

// src/solidus.ts import { createSdk } from '@solidus-network/sdk' export const sdk = createSdk({ mode: 'testnet', chain: { rpcUrl: 'https://rpc.solidus.network', signerPrivateKey: process.env.SOLIDUS_PRIVATE_KEY!, network: 'testnet', }, })

3. Extend the Express Request Type

Add a user property to the Express Request type so that downstream handlers can access authenticated user data.

// src/types.ts import type { Request } from 'express' export interface SolidusUser { did: string credentials: string[] didDocument: { id: string verificationMethod: Array<{ id: string type: string publicKeyHex: string }> } } export interface AuthenticatedRequest extends Request { user?: SolidusUser }

4. Build the Authentication Middleware

This middleware extracts a Bearer token from the Authorization header, verifies it as a Solidus JWT, resolves the user’s DID document, and attaches user information to the request.

// src/middleware/solidus-auth.ts import type { Response, NextFunction } from 'express' import { verify } from '@solidus-network/jwt' import { sdk } from '../solidus' import type { AuthenticatedRequest, SolidusUser } from '../types' const PUBLIC_KEY = process.env.SOLIDUS_PUBLIC_KEY! export async function solidusAuth( req: AuthenticatedRequest, res: Response, next: NextFunction ) { // Extract the Bearer token const authHeader = req.headers.authorization if (!authHeader || !authHeader.startsWith('Bearer ')) { res.status(401).json({ error: 'unauthorized', message: 'Missing or malformed Authorization header', }) return } const token = authHeader.slice(7) // Remove "Bearer " try { // Verify the JWT signature and expiration const decoded = await verify(token, PUBLIC_KEY, { algorithms: ['EdDSA'], }) const did = decoded.sub as string if (!did || !did.startsWith('did:solidus:')) { res.status(401).json({ error: 'unauthorized', message: 'Token does not contain a valid Solidus DID', }) return } // Resolve the DID document from the chain const didDocument = await sdk.did.resolve(did) if (!didDocument) { res.status(401).json({ error: 'unauthorized', message: 'DID not found on chain', }) return } // Attach user info to the request req.user = { did, credentials: (decoded.credentials as string[]) || [], didDocument, } next() } catch (err) { res.status(401).json({ error: 'unauthorized', message: 'Invalid or expired token', }) } }

5. Optional: Require Specific Credentials

A higher-order middleware that checks whether the authenticated user holds specific credential types.

// src/middleware/require-credential.ts import type { Response, NextFunction } from 'express' import type { AuthenticatedRequest } from '../types' export function requireCredential(...requiredTypes: string[]) { return ( req: AuthenticatedRequest, res: Response, next: NextFunction ) => { if (!req.user) { res.status(401).json({ error: 'unauthorized', message: 'Authentication required', }) return } const missing = requiredTypes.filter( (type) => !req.user!.credentials.includes(type) ) if (missing.length > 0) { res.status(403).json({ error: 'forbidden', message: 'Missing required credentials', missing, }) return } next() } }

6. Protected Route: User Profile

An endpoint that returns the authenticated user’s identity information.

// src/routes/profile.ts import { Router } from 'express' import { solidusAuth } from '../middleware/solidus-auth' import type { AuthenticatedRequest } from '../types' const router = Router() router.get( '/api/profile', solidusAuth, (req: AuthenticatedRequest, res) => { const user = req.user! res.json({ did: user.did, credentials: user.credentials, verificationMethods: user.didDocument.verificationMethod.map( (vm) => ({ id: vm.id, type: vm.type, }) ), }) } ) export default router

7. Credential Verification Endpoint

An endpoint that accepts a Verifiable Credential and checks its validity on-chain. This is useful when your service needs to verify credentials presented directly (outside the JWT session flow).

// src/routes/verify-credential.ts import { Router } from 'express' import { sdk } from '../solidus' const router = Router() router.post('/api/verify-credential', async (req, res) => { const { credential } = req.body if (!credential) { res.status(400).json({ error: 'bad_request', message: 'Request body must include a "credential" field', }) return } try { // Verify the credential's signature, expiration, // revocation status, and on-chain anchor const result = await sdk.credentials.verify(credential) res.json({ valid: result.valid, checks: { signature: result.checks.includes('signature'), expiration: result.checks.includes('expiration'), revocation: result.checks.includes('revocation'), onChainAnchor: result.checks.includes('onChainAnchor'), }, issuer: credential.issuer, subject: credential.credentialSubject?.id, type: credential.type, issuanceDate: credential.issuanceDate, expirationDate: credential.expirationDate, }) } catch (err) { res.status(422).json({ error: 'verification_error', message: err instanceof Error ? err.message : 'Verification failed', }) } }) export default router

8. Wire Everything Together

// src/app.ts import express from 'express' import profileRoutes from './routes/profile' import verifyRoutes from './routes/verify-credential' import { solidusAuth } from './middleware/solidus-auth' import { requireCredential } from './middleware/require-credential' import type { AuthenticatedRequest } from './types' const app = express() app.use(express.json()) // Public routes app.get('/api/health', (_req, res) => { res.json({ status: 'ok' }) }) // Credential verification (public — anyone can verify a credential) app.use(verifyRoutes) // Protected routes (require valid JWT) app.use(profileRoutes) // Route requiring a specific credential type app.get( '/api/premium', solidusAuth, requireCredential('KYCCredential'), (req: AuthenticatedRequest, res) => { res.json({ message: 'Welcome, verified user', did: req.user!.did, }) } ) const PORT = process.env.PORT || 3001 app.listen(PORT, () => { console.log(\`Server running on port ${PORT}\`) })

9. Test with cURL

Start the server and test the endpoints.

# Health check curl http://localhost:3001/api/health # Get profile (requires valid JWT) curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \ http://localhost:3001/api/profile # Verify a credential curl -X POST http://localhost:3001/api/verify-credential \ -H "Content-Type: application/json" \ -d '{ "credential": { "@context": ["https://www.w3.org/2018/credentials/v1"], "type": ["VerifiableCredential", "KYCCredential"], "issuer": "did:solidus:testnet:issuer123", "issuanceDate": "2026-01-15T00:00:00Z", "expirationDate": "2027-01-15T00:00:00Z", "credentialSubject": { "id": "did:solidus:testnet:user456", "level": 1 }, "proof": { "type": "Ed25519Signature2020", "created": "2026-01-15T00:00:00Z", "proofValue": "z3FXQje..." } } }'

Project Structure

express-solidus-api/ src/ solidus.ts # SDK instance types.ts # Request type extensions middleware/ solidus-auth.ts # JWT + DID verification require-credential.ts # Credential type gating routes/ profile.ts # GET /api/profile verify-credential.ts # POST /api/verify-credential app.ts # Express app setup .env package.json tsconfig.json

Next Steps

Last updated on