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
Solidus doesn’t publish a JWT package (@solidus-network/jwt was never released to npm — see the
SDK overview). Session tokens are an ordinary application concern, unrelated to the DID
signature verification below, so this guide uses jsonwebtoken, a standard, widely-used package.
npm install @solidus-network/sdk @solidus-network/auth jsonwebtoken express
npm install -D @types/express @types/jsonwebtoken typescript2. Create the SDK Instance
// src/solidus.ts
import { createSdk } from '@solidus-network/sdk'
export const sdk = createSdk({
mode: 'testnet',
rpcUrl: 'https://rpc.solidus.network',
signerPrivateKey: process.env.SOLIDUS_PRIVATE_KEY!,
})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 jwt from 'jsonwebtoken'
import { sdk } from '../solidus'
import type { AuthenticatedRequest, SolidusUser } from '../types'
const SESSION_JWT_SECRET = process.env.SESSION_JWT_SECRET!
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 session JWT this API issued at login (issuance is
// application-specific — sign { sub: did, credentials } with the same
// SESSION_JWT_SECRET using jwt.sign() from the 'jsonwebtoken' package)
const decoded = jwt.verify(token, SESSION_JWT_SECRET) as jwt.JwtPayload
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 credential ID 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 { credentialId } = req.body
if (!credentialId) {
res.status(400).json({
error: 'bad_request',
message: 'Request body must include a "credentialId" field',
})
return
}
try {
// Verify signature, expiry, and revocation status by credential ID
const result = await sdk.credentials.verify(credentialId)
res.json({
valid: result.valid,
checks: result.checks, // { signature, expiry, revocation }
credentialId: result.credentialId,
error: result.error,
})
} 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 (by ID)
curl -X POST http://localhost:3001/api/verify-credential \
-H "Content-Type: application/json" \
-d '{ "credentialId": "urn:solidus:credential:..." }'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