@wepublish/express-external-app-auth
v0.1.0
Published
Express middleware for Wepublish External App JWT auth (JWKS, EdDSA).
Downloads
101
Keywords
Readme
@wepublish/express-external-app-auth
Express middleware for verifying Wepublish External App JWTs in iframe-embedded applications.
This package validates JWTs issued by a Wepublish CMS using:
- EdDSA (Ed25519) signature verification
- Remote JWKS (auto-cached via jose)
aud,iss,exp,nbfvalidation- Optional
/external-apps/userinfofetching - Optional iframe origin enforcement
- TypeScript support (full typings included)
Designed for backend servers of external applications embedded in Wepublish CMS via iframe.
Installation
npm install @wepublish/express-external-app-authPeer dependency:
npm install expressQuick Start
import express from "express";
import cookieParser from "cookie-parser";
import {
iframeEmbeddingHeaders,
wepublishExternalAppAuth,
} from "@wepublish/express-external-app-auth";
const app = express();
app.use(cookieParser());
const allowedCMSOrigins = ["https://cms.wepublish.ch"];
app.use(iframeEmbeddingHeaders(allowedCMSOrigins));
app.use(
wepublishExternalAppAuth({
cmsApiBaseUrl: "https://api.wepublish.ch",
jwksUrl: "https://api.wepublish.ch/external-apps/jwks.json",
issuers: ["https://api.wepublish.ch"],
expectedAudience: "https://external-app.example.com",
allowedParentOrigins: allowedCMSOrigins,
tokenSources: {
header: true,
cookieName: "wepublish_extapp",
},
mode: "verify-jwt",
})
);
app.get("/api/me", (req, res) => {
res.json(req.wepublishAuth);
});
app.listen(3000);How It Works
- The CMS issues a short-lived JWT via
createExternalAppToken - The token is delivered to the external app (preferably via
postMessage) - The frontend sends the token to your backend (
Authorizationheader recommended) - This middleware:
- Verifies signature using JWKS
- Validates
aud,iss,exp - Optionally fetches
/external-apps/userinfo - Attaches
req.wepublishAuth
Middleware
wepublishExternalAppAuth(options)
Main authentication middleware.
Options
type WepublishAuthOptions = {
cmsApiBaseUrl: string;
jwksUrl: string;
issuers?: string[];
expectedAudience: string;
allowedParentOrigins?: string[];
tokenSources?: {
header?: boolean;
cookieName?: string;
queryParam?: string;
};
mode?: "verify-jwt" | "fetch-userinfo";
userInfoEndpoint?: string;
clockToleranceSec?: number;
};Modes
verify-jwt (default)
Only verifies the JWT locally via JWKS. Fastest and fully stateless.
fetch-userinfo
After JWT verification, calls:
GET /external-apps/userinfo
Authorization: Bearer <token>Use this if:
- You want authoritative role/permission resolution from CMS
- You prefer server-side user state validation
iframeEmbeddingHeaders(allowedOrigins)
Adds security headers to restrict embedding via CSP:
Content-Security-Policy: frame-ancestors https://cms.wepublish.ch;Example:
app.use(iframeEmbeddingHeaders(["https://cms.wepublish.ch"]));Accessing Auth Data
After successful authentication:
req.wepublishAuth = {
jwt: JWTPayload,
token: string,
user?: UserInfo
}Example:
app.get("/api/roles", (req, res) => {
const roles = req.wepublishAuth?.jwt.roles;
res.json({ roles });
});Recommended Token Delivery Pattern (Secure)
Avoid
https://external-app.example.com?token=...Query parameters leak via:
- Browser history
- Logs
- Reverse proxies
- Referrer headers
Recommended
Use postMessage from CMS to external app:
iframe.contentWindow.postMessage(
{ type: "WE_PUBLISH_TOKEN", token },
"https://external-app.example.com"
);Then send token to backend via:
Authorization: Bearer <token>Security Features
- Signature verification via
jose - Remote JWKS with automatic caching
audvalidation (prevents token reuse across apps)issvalidation (optional)exp/nbfenforcement- Clock skew tolerance
- Optional origin enforcement
- CSP
frame-ancestorsheader helper
Production Recommendations
- Use short-lived tokens (5-15 min recommended). If CMS issues 240-minute tokens, consider rotating them via iframe refresh mechanism.
- Validate by origin (not full URL path). Use
expectedAudience: "https://external-app.example.com". Avoid fragile full-path matching. - Disable query param tokens. Simply do not configure
queryParamunless absolutely necessary. - Rate-limit
/external-apps/userinfoif usingfetch-userinfomode.
Example JWT Claims
Typical claims:
{
"sub": "user-id",
"aud": "https://external-app.example.com",
"iss": "https://api.wepublish.ch",
"exp": 1712345678,
"roles": ["admin"]
}TypeScript Support
This package augments Express Request:
req.wepublishAuth; // WepublishAuthContext | undefinedNo manual typing required.
Compatibility
- Node.js >= 18
- Express >= 4.18
- ESM + CJS supported
License
MIT
Contributing
PRs welcome.
Please ensure:
- Strong type safety
- No unnecessary runtime dependencies
- No breaking API changes without version bump
