Part of epic #3306.
Problem
Agents that hit a protected endpoint on api.worldmonitor.app get a 401 or similar but have no standard way to learn (a) which authorization server issues tokens for this resource, (b) what scopes they must request, or (c) what resource identifier to put in the token aud claim. RFC 9728 defines /.well-known/oauth-protected-resource for exactly this.
We already use Clerk for auth, and Clerk exposes a standards-compliant OIDC discovery document — so we have the upstream pieces; we just need to publish the resource-side metadata.
Fix
Serve /.well-known/oauth-protected-resource at the origin root with at minimum:
{
"resource": "https://api.worldmonitor.app/",
"authorization_servers": [
"https://clerk.worldmonitor.app"
],
"scopes_supported": [
"read:public",
"read:pro"
],
"bearer_methods_supported": ["header"],
"resource_documentation": "https://worldmonitor.app/docs/api/auth"
}
Exact values to confirm during implementation:
- The Clerk OIDC issuer for our prod instance (check Clerk dashboard).
- The actual scope namespace we want to advertise. Today our gate logic uses
isCallerPremium (Clerk org:pro role OR Convex tier>=1) rather than OAuth scopes — so we either (a) advertise a pro scope and teach the gate to also honour it, or (b) advertise only read:public and keep premium out of this surface for now.
Implementation
Acceptance criteria
References
Part of epic #3306.
Problem
Agents that hit a protected endpoint on
api.worldmonitor.appget a 401 or similar but have no standard way to learn (a) which authorization server issues tokens for this resource, (b) what scopes they must request, or (c) what resource identifier to put in the tokenaudclaim. RFC 9728 defines/.well-known/oauth-protected-resourcefor exactly this.We already use Clerk for auth, and Clerk exposes a standards-compliant OIDC discovery document — so we have the upstream pieces; we just need to publish the resource-side metadata.
Fix
Serve
/.well-known/oauth-protected-resourceat the origin root with at minimum:{ "resource": "https://api.worldmonitor.app/", "authorization_servers": [ "https://clerk.worldmonitor.app" ], "scopes_supported": [ "read:public", "read:pro" ], "bearer_methods_supported": ["header"], "resource_documentation": "https://worldmonitor.app/docs/api/auth" }Exact values to confirm during implementation:
isCallerPremium(Clerkorg:prorole OR Convextier>=1) rather than OAuth scopes — so we either (a) advertise aproscope and teach the gate to also honour it, or (b) advertise onlyread:publicand keep premium out of this surface for now.Implementation
public/.well-known/oauth-protected-resourceOR edge function atapi/.well-known/oauth-protected-resource.tsif we want to interpolate env-based values (prefer static if values are stable).Content-Type: application/json.http://openid.net/specs/connect/1.0/protected-resource-metadata(or the RFC 9728 reserved rel).middleware.tsPUBLIC_API_PATHS allows this.well-knownpath (see memory: middleware short-UA guard).Acceptance criteria
curl -s https://worldmonitor.app/.well-known/oauth-protected-resourcereturns valid JSON conforming to RFC 9728.resourcefield matches the actual API origin (https://api.worldmonitor.app/).authorization_servers[0]/.well-known/openid-configurationresolves (smoke check).isitagentready.com"OAuth Protected Resource Metadata" check passes.References