API Gateway
Custom Authorizer
CORS
401 Error
Access-Control-Allow-Origin

401 return from an API Gateway Custom Authorizer is missing 'Access-Control-Allow-Origin' header

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

When an API Gateway custom authorizer rejects a request, the 401 response is usually generated by API Gateway itself before your backend integration runs. That is why the normal CORS headers from your Lambda or HTTP backend never appear. To include Access-Control-Allow-Origin on those 401 responses, you need to configure the gateway-level error responses, not just the integration response.

Why the Header Is Missing

In a browser, a failed authenticated request still has to satisfy CORS rules. If API Gateway returns a 401 without Access-Control-Allow-Origin, the browser often reports a CORS failure instead of exposing the real authorization error to your frontend.

This happens because the flow looks like this:

  1. the browser sends the request
  2. API Gateway runs the custom authorizer
  3. authorization fails before the integration is invoked
  4. API Gateway generates the 401 directly

Since your Lambda or upstream service never runs, any CORS headers you set there are irrelevant for this response path.

Configure Gateway Responses for 401 Errors

For REST APIs in API Gateway, the usual fix is to customize the UNAUTHORIZED and often ACCESS_DENIED gateway responses. Add the CORS headers there so API Gateway includes them on authorizer-generated failures.

In the console, the relevant path is typically:

  • API Gateway
  • your REST API
  • 'Gateway Responses'
  • edit UNAUTHORIZED
  • add Access-Control-Allow-Origin
  • usually also add Access-Control-Allow-Headers and Access-Control-Allow-Methods

If your frontend sends credentials, do not use * blindly for the origin. Use the exact allowed origin instead.

Example With CloudFormation or SAM

A configuration example looks like this:

yaml
1GatewayResponses:
2  UnauthorizedResponse:
3    Type: AWS::ApiGateway::GatewayResponse
4    Properties:
5      RestApiId: !Ref ApiGatewayRestApi
6      ResponseType: UNAUTHORIZED
7      StatusCode: '401'
8      ResponseParameters:
9        gatewayresponse.header.Access-Control-Allow-Origin: "'https://app.example.com'"
10        gatewayresponse.header.Access-Control-Allow-Headers: "'Content-Type,Authorization'"
11        gatewayresponse.header.Access-Control-Allow-Methods: "'GET,POST,OPTIONS'"
12  AccessDeniedResponse:
13    Type: AWS::ApiGateway::GatewayResponse
14    Properties:
15      RestApiId: !Ref ApiGatewayRestApi
16      ResponseType: ACCESS_DENIED
17      StatusCode: '403'
18      ResponseParameters:
19        gatewayresponse.header.Access-Control-Allow-Origin: "'https://app.example.com'"
20        gatewayresponse.header.Access-Control-Allow-Headers: "'Content-Type,Authorization'"
21        gatewayresponse.header.Access-Control-Allow-Methods: "'GET,POST,OPTIONS'"

The extra quoting on the header values is important because API Gateway expects mapping-expression-style string literals in these response parameters.

Do Not Forget Preflight

Even after fixing the 401 path, your OPTIONS responses still need proper CORS headers. A browser may send a preflight request before the real authenticated request, especially when custom headers such as Authorization are involved.

So the full CORS setup usually requires:

  • an OPTIONS method or automatic CORS handling
  • allowed origin headers on success responses
  • allowed origin headers on gateway-generated error responses

If any one of those pieces is missing, the frontend may still see a generic CORS failure.

Lambda Authorizer Output Does Not Solve This

A common misconception is that the custom authorizer can simply return extra headers with its deny result. For REST API custom authorizers, the authorizer mainly returns an IAM policy document and optional context, not the final HTTP response headers for the 401 generated by API Gateway.

That is why the fix belongs at the API Gateway response layer.

Common Pitfalls

The biggest pitfall is adding CORS headers only in the backend integration response. That helps for normal application responses, but not for authorizer-generated 401 responses.

Another pitfall is configuring only UNAUTHORIZED and forgetting ACCESS_DENIED or the default 4XX path. Different failure modes may return different gateway response types.

Developers also use Access-Control-Allow-Origin: * together with credentialed requests. Browsers reject that combination, so use an explicit origin when credentials are involved.

Finally, after changing gateway responses, redeploy the API if your deployment model requires it. Many CORS fixes fail simply because the updated gateway configuration was never deployed.

Summary

  • A custom authorizer 401 is usually generated by API Gateway before your backend runs.
  • Because of that, backend CORS headers do not appear on the 401 response.
  • Configure API Gateway Gateway Responses, especially UNAUTHORIZED and often ACCESS_DENIED, to add CORS headers.
  • Make sure preflight OPTIONS handling is correct as well.
  • If credentials are involved, use a specific allowed origin instead of *.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design