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 typescript2. 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 router7. 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 router8. 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.jsonNext Steps
- Next.js Integration — full-stack integration with the App Router
- Credential Flow — understand the credential lifecycle
- Webhooks — receive real-time notifications for verification events