Skip to content

Commit 16ed01f

Browse files
committed
Ask Claude to implement CORS as needed by public clients.
prompt: To fully support "public" clients, we need to support CORS on all the endpoints implemented by OAuthProvider. For convenience, we should also automatically enable CORS on API endpoints implemented by the app. (We should not, however, enable CORS on anything handled by the default handler.) Claude wasn't aware of the best way to mutate Response headers. prompt: The way you construct the final `Response` here could be lossy if the platform supports properties other than `status`, `statusText`, and `headers`. The better way to modify response headers it to first use `response = new Response(response.body, response)` to make the response mutable, then use `response.headers.set(name, value)` to modify the response headers directly. Initially Claude allowed credentials but only the GET, POST, and OPTIONS methods, and only the `Content-Type` and `Authorization` headers. prompt: Two things: (1) Let's not allow credentials. APIs should be authenticated strictly based on the access token, not the user's cookies. (2) Let's set access-control-allow-methods to `*` to allow all methods. prompt: Let's also allow all headers. prompt: Oops, the `Authorization` header needs to be listed explicitly, it is special and not included in `*`. But we need to allow it for passing access tokens.
1 parent a103ed0 commit 16ed01f

File tree

1 file changed

+59
-4
lines changed

1 file changed

+59
-4
lines changed

oauth-provider.ts

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -651,25 +651,50 @@ class OAuthProviderImpl {
651651
async fetch(request: Request, env: any, ctx: ExecutionContext): Promise<Response> {
652652
const url = new URL(request.url);
653653

654+
// Special handling for OPTIONS requests (CORS preflight)
655+
if (request.method === 'OPTIONS') {
656+
// For API routes and OAuth endpoints, respond with CORS headers
657+
if (this.isApiRequest(url) ||
658+
url.pathname === '/.well-known/oauth-authorization-server' ||
659+
this.isTokenEndpoint(url) ||
660+
(this.options.clientRegistrationEndpoint && this.isClientRegistrationEndpoint(url))) {
661+
662+
// Create an empty 204 No Content response with CORS headers
663+
return this.addCorsHeaders(
664+
new Response(null, {
665+
status: 204,
666+
headers: { 'Content-Length': '0' }
667+
}),
668+
request
669+
);
670+
}
671+
672+
// For other routes, pass through to the default handler
673+
}
674+
654675
// Handle .well-known/oauth-authorization-server
655676
if (url.pathname === '/.well-known/oauth-authorization-server') {
656-
return this.handleMetadataDiscovery(url);
677+
const response = await this.handleMetadataDiscovery(url);
678+
return this.addCorsHeaders(response, request);
657679
}
658680

659681
// Handle token endpoint
660682
if (this.isTokenEndpoint(url)) {
661-
return this.handleTokenRequest(request, env);
683+
const response = await this.handleTokenRequest(request, env);
684+
return this.addCorsHeaders(response, request);
662685
}
663686

664687
// Handle client registration endpoint
665688
if (this.options.clientRegistrationEndpoint &&
666689
this.isClientRegistrationEndpoint(url)) {
667-
return this.handleClientRegistration(request, env);
690+
const response = await this.handleClientRegistration(request, env);
691+
return this.addCorsHeaders(response, request);
668692
}
669693

670694
// Check if it's an API request
671695
if (this.isApiRequest(url)) {
672-
return this.handleApiRequest(request, env, ctx);
696+
const response = await this.handleApiRequest(request, env, ctx);
697+
return this.addCorsHeaders(response, request);
673698
}
674699

675700
// Inject OAuth helpers into env if not already present
@@ -678,6 +703,7 @@ class OAuthProviderImpl {
678703
}
679704

680705
// Call the default handler based on its type
706+
// Note: We don't add CORS headers to default handler responses
681707
if (this.defaultHandlerType === HandlerType.EXPORTED_HANDLER) {
682708
// It's an object with a fetch method
683709
return (this.options.defaultHandler as ExportedHandler).fetch(request, env, ctx);
@@ -783,6 +809,35 @@ class OAuthProviderImpl {
783809
}
784810
}
785811

812+
/**
813+
* Adds CORS headers to a response
814+
* @param response - The response to add CORS headers to
815+
* @param request - The original request
816+
* @returns A new Response with CORS headers added
817+
*/
818+
private addCorsHeaders(response: Response, request: Request): Response {
819+
// Get the Origin header from the request
820+
const origin = request.headers.get('Origin');
821+
822+
// If there's no Origin header, return the original response
823+
if (!origin) {
824+
return response;
825+
}
826+
827+
// Create a new response that copies all properties from the original response
828+
// This makes the response mutable so we can modify its headers
829+
const newResponse = new Response(response.body, response);
830+
831+
// Add CORS headers
832+
newResponse.headers.set('Access-Control-Allow-Origin', origin);
833+
newResponse.headers.set('Access-Control-Allow-Methods', '*');
834+
// Include Authorization explicitly since it's not included in * for security reasons
835+
newResponse.headers.set('Access-Control-Allow-Headers', 'Authorization, *');
836+
newResponse.headers.set('Access-Control-Max-Age', '86400'); // 24 hours
837+
838+
return newResponse;
839+
}
840+
786841
/**
787842
* Handles the OAuth metadata discovery endpoint
788843
* Implements RFC 8414 for OAuth Server Metadata

0 commit comments

Comments
 (0)