|
1 | 1 | # @seamless-auth/server-express |
2 | 2 |
|
3 | | -Drop-in Express adapter for Seamless Auth “server mode” authentication. |
| 3 | +### Seamless Auth Express Adapter |
4 | 4 |
|
5 | | -```js |
| 5 | +A secure, passwordless **server-side adapter** that connects your Express API to a private Seamless Auth Server. |
| 6 | + |
| 7 | +It proxies all authentication flows, manages signed cookies, and gives you out-of-the-box middleware for verifying users and enforcing roles. |
| 8 | + |
| 9 | +> **npm:** https://www.npmjs.com/package/@seamless-auth/express |
| 10 | +> **Docs:** https://docs.seamlessauth.com |
| 11 | +> **Repo:** https://github.com/fells-code/seamless-auth-server |
| 12 | +
|
| 13 | + |
| 14 | +> Couple with https://github.com/fells-code/seamless-auth/react for an end to end seamless experience |
| 15 | +
|
| 16 | +> Or get a full starter application with https://github.com/fells-code/create-seamless |
| 17 | +
|
| 18 | +--- |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Installation |
| 23 | + |
| 24 | +```bash |
| 25 | +npm install @seamless-auth/server-express |
| 26 | +# or |
| 27 | +yarn add @seamless-auth/server-express |
| 28 | +``` |
| 29 | + |
| 30 | + |
| 31 | +## Quick Example |
| 32 | + |
| 33 | +```ts |
6 | 34 | import express from "express"; |
7 | | -import createSeamlessAuthServer from "@seamless-auth/server-express"; |
| 35 | +import cookieParser from "cookie-parser"; |
| 36 | +import createSeamlessAuthServer, { requireAuth, requireRole } from "@seamless-auth/server-express"; |
8 | 37 |
|
9 | 38 | const app = express(); |
10 | | -app.use("/auth", createSeamlessAuthServer({ |
11 | | - authServerUrl: process.env.AUTH_SERVER_URL, |
12 | | - cookieDomain: ".myapp.com" |
13 | | -})); |
| 39 | +app.use(cookieParser()); |
| 40 | + |
| 41 | +// Public Seamless Auth endpoints |
| 42 | +app.use("/auth", createSeamlessAuthServer({ authServerUrl: process.env.AUTH_SERVER_URL! })); |
| 43 | + |
| 44 | +// Everything after this line requires authentication |
| 45 | +app.use(requireAuth()); |
| 46 | + |
| 47 | +app.get("/api/me", (req, res) => res.json({ user: (req as any).user })); |
| 48 | +app.get("/admin", requireRole("admin"), (req, res) => res.json({ message: "Welcome admin!" })); |
| 49 | + |
| 50 | +app.listen(5000, () => console.log("Portal API running on :5000")); |
| 51 | +``` |
| 52 | + |
| 53 | +--- |
| 54 | + |
| 55 | +# Full Documentation |
| 56 | + |
| 57 | +## Overview |
| 58 | + |
| 59 | +`@seamless-auth/express` lets your backend API act as an authentication and authorization server using Seamless Auth. |
| 60 | + |
| 61 | +It transparently proxies and validates authentication flows so your frontend can use a single API endpoint for: |
| 62 | + |
| 63 | +- Login / Registration / Logout |
| 64 | +- User introspection (`/auth/me`) |
| 65 | +- Session cookies (signed JWTs) |
| 66 | +- Role & permission guards |
| 67 | +- Internal Auth Server communication (JWKS + service tokens) |
| 68 | + |
| 69 | +Everything happens securely between your API and a private Seamless Auth Server. |
| 70 | + |
| 71 | + |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +## Architecture |
| 76 | + |
| 77 | +``` |
| 78 | +[Frontend App] |
| 79 | + │ |
| 80 | + ▼ |
| 81 | +[Your Express API] |
| 82 | + ├─ createSeamlessAuthServer() ← mounts /auth routes |
| 83 | + ├─ requireAuth() ← verifies signed cookie JWT |
| 84 | + ├─ requireRole('admin') ← role-based guard |
| 85 | + └─ getSeamlessUser() ← calls Auth Server |
| 86 | + │ |
| 87 | + ▼ |
| 88 | +[Private Seamless Auth Server] |
| 89 | +``` |
| 90 | + |
| 91 | +--- |
| 92 | + |
| 93 | +## Environment Variables |
| 94 | + |
| 95 | +| Variable | Description | Example | |
| 96 | +|-----------|--------------|----------| |
| 97 | +| `AUTH_SERVER_URL` | Base URL of your Seamless Auth Server | `https://auth.client.com` | |
| 98 | +| `SEAMLESS_COOKIE_SIGNING_KEY` | Secret key for signing JWT cookies | `base64:...` | |
| 99 | +| `SERVICE_JWT_PRIVATE_KEY` | Private key for API → Auth Server JWTs | RSA PEM | |
| 100 | +| `SERVICE_JWT_KEYID` | Key ID for JWKS | `service-main` | |
| 101 | +| `COOKIE_DOMAIN` | Domain for cookies | `.client.com` | |
| 102 | + |
| 103 | +--- |
| 104 | + |
| 105 | +## API Reference |
| 106 | + |
| 107 | +### `createSeamlessAuthServer(options)` |
| 108 | + |
| 109 | +Mounts an Express router exposing the full Seamless Auth flow: |
| 110 | + |
| 111 | +- `/auth/login/start` |
| 112 | +- `/auth/login/finish` |
| 113 | +- `/auth/webauthn/...` |
| 114 | +- `/auth/registration/...` |
| 115 | +- `/auth/me` |
| 116 | +- `/auth/logout` |
| 117 | + |
| 118 | +**Options** |
| 119 | + |
| 120 | +```ts |
| 121 | +{ |
| 122 | + authServerUrl: string; // required |
| 123 | + cookieDomain?: string; |
| 124 | + cookieNameOverrides?: { |
| 125 | + preauth?: string; |
| 126 | + registration?: string; |
| 127 | + access?: string; |
| 128 | + }; |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +### `requireAuth(cookieName?: string)` |
| 135 | + |
| 136 | +Middleware that validates the signed access cookie (`seamless_auth_access` by default) |
| 137 | +and attaches the decoded user payload to `req.user`. |
| 138 | + |
| 139 | +```ts |
| 140 | +app.get("/api/profile", requireAuth(), (req, res) => { |
| 141 | + res.json({ user: req.user }); |
| 142 | +}); |
| 143 | +``` |
| 144 | + |
| 145 | +--- |
| 146 | + |
| 147 | +### `requireRole(role: string, cookieName?: string)` |
| 148 | + |
| 149 | +Role-based authorization guard. |
| 150 | +Blocks non-matching roles with HTTP 403. |
| 151 | + |
| 152 | +```ts |
| 153 | +app.get("/admin", requireRole("admin"), (req, res) => { |
| 154 | + res.json({ message: "Welcome admin!" }); |
| 155 | +}); |
| 156 | +``` |
| 157 | + |
| 158 | +--- |
| 159 | + |
| 160 | +### `getSeamlessUser(req, authServerUrl, cookieName?)` |
| 161 | + |
| 162 | +Calls the Auth Server’s `/internal/session/introspect` endpoint using a signed service JWT |
| 163 | +and returns the Seamless user object. |
| 164 | + |
| 165 | +```ts |
| 166 | +const user = await getSeamlessUser(req, process.env.AUTH_SERVER_URL!); |
| 167 | +``` |
| 168 | + |
| 169 | +User shape |
| 170 | +```ts |
| 171 | +{ |
| 172 | + id: string; |
| 173 | + email: string; |
| 174 | + phone: string; |
| 175 | + roles: string[] |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +## End-to-End Flow |
| 180 | + |
| 181 | +1. **Frontend** → `/auth/login/start` |
| 182 | + → API proxies to Seamless Auth Server |
| 183 | + → sets short-lived pre-auth cookie. |
| 184 | + |
| 185 | +2. **Frontend** → `/auth/webauthn/finish` |
| 186 | + → API proxies, validates, sets access cookie (`seamless_auth_access`). |
| 187 | + |
| 188 | +3. **Subsequent API calls** → `/api/...` |
| 189 | + → `requireAuth()` verifies cookie and attaches user. |
| 190 | + → Role routes use `requireRole()`. |
| 191 | + |
| 192 | +--- |
| 193 | + |
| 194 | +## Local Development |
| 195 | + |
| 196 | +In order to develop with your Seamless Auth server instance, you will need to have: |
| 197 | + |
| 198 | +- Created an account @ https://dashboard.seamlessauth.com |
| 199 | +- Created a new Seamless Auth application |
| 200 | + |
| 201 | +Example env: |
| 202 | + |
| 203 | +```bash |
| 204 | +AUTH_SERVER_URL=http://https://<identifier>.seamlessauth.com # Found in the portal |
| 205 | +COOKIE_DOMAIN=localhost # Or frontend domain in prod |
| 206 | +SEAMLESS_COOKIE_SIGNING_KEY=local-secret-key # Found in the portal |
| 207 | +``` |
| 208 | + |
| 209 | +--- |
| 210 | + |
| 211 | +## Example Middleware Stack |
| 212 | + |
| 213 | +```ts |
| 214 | +const AUTH_SERVER_URL = process.env.AUTH_SERVER_URL!; |
| 215 | +app.use(cors({ origin: "https://localhost:5001", credentials: true })); |
| 216 | +app.use(express.json()); |
| 217 | +app.use(cookieParser()); |
| 218 | +app.use("/auth", createSeamlessAuthServer({ authServerUrl: AUTH_SERVER_URL })); |
| 219 | +app.use(requireAuth()); |
| 220 | +``` |
| 221 | + |
| 222 | +--- |
| 223 | + |
| 224 | +## Security Model |
| 225 | + |
| 226 | +| Layer | Auth Mechanism | Signed By | |
| 227 | +|--------|----------------|------------| |
| 228 | +| **Frontend ↔ API** | Signed JWT in HttpOnly cookie (HS256) | Client API | |
| 229 | +| **API ↔ Auth Server** | Bearer Service JWT (RS256) | API’s private key | |
| 230 | +| **Auth Server** | Validates service tokens via JWKS | Seamless Auth JWKS | |
| 231 | + |
| 232 | +All tokens and cookies are stateless and cryptographically verifiable. |
| 233 | + |
| 234 | +--- |
| 235 | + |
| 236 | +## Testing |
| 237 | + |
| 238 | +You can mock `requireAuth` and test Express routes via `supertest`. |
| 239 | + |
| 240 | +Example: |
| 241 | + |
| 242 | +```ts |
| 243 | +import { requireAuth } from "@seamless-auth/server-express"; |
| 244 | +app.get("/api/test", requireAuth(), (req, res) => res.json({ ok: true })); |
| 245 | +``` |
| 246 | + |
| 247 | +--- |
| 248 | + |
| 249 | +## Roadmap |
| 250 | + |
| 251 | +| Feature | Status | |
| 252 | +|----------|---------| |
| 253 | +| JWKS-verified response signing | ✅ | |
| 254 | +| OIDC discovery & SSO readiness | planned | |
| 255 | +| Federation (Google / Okta) | future | |
| 256 | +| Multi-framework adapters (Next.js / Fastify) | coming soon | |
| 257 | + |
| 258 | +--- |
| 259 | + |
| 260 | +## License |
| 261 | + |
| 262 | +MIT © 2025 Fells Code LLC |
| 263 | +Part of the **Seamless Auth** ecosystem. |
| 264 | + |
0 commit comments