Skip to content
Last updated Give Feedback

Authentication

ecommus uses JWT (JSON Web Tokens) with short-lived access tokens and long-lived refresh tokens stored in httpOnly cookies.

1. POST /api/admin/auth/login { email, password }
→ 200 { accessToken: "eyJ...", expiresIn: 900 }
→ Set-Cookie: refreshToken=...; HttpOnly; Secure; SameSite=Strict
2. Include in subsequent requests:
Authorization: Bearer <accessToken>
X-Tenant-Id: <tenantId>
3. When access token expires (15 min):
POST /api/admin/auth/refresh (sends refreshToken cookie automatically)
→ 200 { accessToken: "eyJ...", expiresIn: 900 }
4. POST /api/admin/auth/logout
→ Invalidates refresh token in DB
→ Clears refreshToken cookie
SettingDefaultEnv Variable
Access token expiry15 minutesJWT_EXPIRY
Refresh token expiry30 daysJWT_REFRESH_EXPIRY
AlgorithmHS256
SecretJWT_SECRET (min 32 chars)

All admin routes use requireAuth as a preHandler. It:

  1. Reads the Authorization: Bearer <token> header
  2. Verifies the JWT signature with JWT_SECRET
  3. Checks token expiry
  4. Sets req.user = { id, email, tenantId, role }
  5. Returns 401 Unauthorized if any check fails
// How to protect a custom route
app.get('/api/admin/my-route', {
preHandler: [requireAuth, resolveTenant, requireTenant],
handler: async (req, reply) => {
const userId = req.user.id; // safe to access after requireAuth
// ...
}
});

Customer auth uses the same JWT mechanism but a separate token family:

POST /api/storefront/auth/register { email, password, firstName, lastName }
POST /api/storefront/auth/login { email, password }

Customer JWTs have role: 'customer' and are scoped to the tenant.

  • Passwords are hashed with argon2id (never MD5, bcrypt, or plain SHA)
  • Refresh tokens are stored in the database and can be revoked (logout or security event)
  • All auth endpoints are rate-limited (10 req/min per IP)
  • CORS is configured to only allow FRONTEND_URL and ADMIN_URL origins
  • JWT_SECRET must be at least 32 characters — startup will fail otherwise