Skip to main content

CORS, Rate Limiting, and Proxy

The OneRoster® service has three cross-cutting runtime controls that govern how requests are admitted into the application:

  • CORS origin allowlisting (CORS_ORIGINS)
  • Per-IP rate limiting on the /ims/oneroster/* routes (RATE_LIMIT_WINDOW_MS and RATE_LIMIT_MAX_REQUESTS)
  • Trust-proxy behavior when the service sits behind a reverse proxy (TRUST_PROXY)

All three are controlled by environment variables. See Environment variables for the full reference. This page describes the runtime behavior so operators can predict it and diagnose issues.

CORS origins

CORS_ORIGINS is a comma-separated list of origins (scheme, host, and port) that the service accepts as CORS origins. The value in .env.example is:

CORS_ORIGINS=http://localhost:3000

Behavior at startup:

  • If CORS_ORIGINS is empty or unset, the service allows all origins (equivalent to cors({ origin: true })). This is convenient for local development but is not recommended for production.
  • If CORS_ORIGINS is set, the service only allows exact matches from the comma-separated list. Requests with a disallowed Origin header are rejected with a CORS error. Requests with no Origin header (curl, Postman, server-to-server) are always allowed through.

Origins are matched on the full string (scheme, host, and port). They are not wildcard-expanded. To allow a Swagger UI served alongside the ODS / API, include its origin:

CORS_ORIGINS=https://oneroster.example.org,https://odsapi.example.org:56641

JWT validation still applies regardless of origin. CORS only decides whether the browser is allowed to read the response.

Rate limiting

The /ims/oneroster/* routes are rate-limited by express-rate-limit using the IP address of the client. Other routes (/health-check, /docs, /swagger.json, /) are not rate-limited.

VariableDefaultBehavior
RATE_LIMIT_WINDOW_MS60000 (1 minute)Sliding window length.
RATE_LIMIT_MAX_REQUESTS100Maximum requests per IP per window.

Response headers follow the RFC draft for rate-limit headers (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset). The legacy X-RateLimit-* headers are disabled.

When a client exceeds the limit, the service returns an IMS-formatted error:

{
"imsx_codeMajor": "failure",
"imsx_severity": "error",
"imsx_description": "Too many requests. Server is busy, retry later."
}

For deployments where clients routinely retrieve large result sets through pagination, raise RATE_LIMIT_MAX_REQUESTS or widen the window rather than disabling the limit entirely. Document the adjusted values to the integrators consuming the API.

Trust proxy

The OneRoster service reads X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-Prefix to generate self-referencing discovery URLs (the Swagger UI servers entry and the JSON returned by GET /). Express only honors these headers when the application has trust proxy enabled.

TRUST_PROXYBehavior
false (default)X-Forwarded-* headers are ignored. Discovery URLs use the protocol and host the Node process observes directly.
trueX-Forwarded-* headers are trusted. Discovery URLs reflect the public-facing protocol and host from the proxy.

Set TRUST_PROXY=true whenever the service is deployed behind:

  • IIS (both iisnode and ARR reverse-proxy setups)
  • NGINX (including the bundled stack/nginx-compose.yml)
  • Any other reverse proxy terminating TLS
warning

Do not set TRUST_PROXY=true when the service is exposed directly to untrusted networks without a trusted proxy in front of it. In that case, callers could forge X-Forwarded-* headers to influence discovery URLs.

For the ARR reverse-proxy setup on IIS, also register HTTP_X_FORWARDED_PROTO and HTTP_X_FORWARDED_HOST under URL Rewrite View Server Variables so the rewrite rules are allowed to set them. The IIS deployment guide walks through this step. See Deploy on IIS.

How API_BASE_PATH interacts with proxy behavior

When the service is hosted under a virtual directory (for example, https://example.org/oneroster), discovery URLs must include the virtual path. The service resolves this in the following order:

  1. X-Forwarded-Prefix request header, when TRUST_PROXY=true
  2. API_BASE_PATH environment variable
  3. Empty (service treated as hosted at root)

If the proxy does not emit X-Forwarded-Prefix, set API_BASE_PATH explicitly so the generated URLs include the virtual-directory segment.