# Fix AuthorizerConfigurationException in API Gateway

> AuthorizerConfigurationException means API Gateway couldn't call your Lambda authorizer or couldn't read its response. Find the cause and fix it.

By Boris Tane, Founder of Polylane · Published October 18, 2026 · 11 min read
Canonical: https://polylane.com/learn/troubleshooting/aws-authorizerconfigurationexception/

```text
AuthorizerConfigurationException
```

AuthorizerConfigurationException is API Gateway's AUTHORIZER_CONFIGURATION_ERROR response, sent as a 500. It means API Gateway either couldn't invoke your Lambda authorizer or got back a response it couldn't parse. The usual fixes are to grant apigateway.amazonaws.com lambda:InvokeFunction on the authorizer function, and to return a plain object with principalId, policyDocument and flat context values.

Your client gets a 500 Internal Server Error, and the error type you commonly see in the x-amzn-ErrorType header is AuthorizerConfigurationException. It fires on methods or routes protected by a Lambda authorizer (the older name is custom authorizer). It often appears straight after a deploy, a stack rebuild or an authorizer rewrite, even when the backend integration is healthy.

The backend is never reached. API Gateway stops at the authorization step because something about the authorizer itself is broken: the call to the function, or what the function sent back. Your token logic is probably fine. The wiring around it is wrong.

## What AuthorizerConfigurationException is telling you

API Gateway prints this error. It is the client-facing form of the `AUTHORIZER_CONFIGURATION_ERROR` gateway response. The [gateway response types reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/supported-gateway-response-types.html) lists its default status as 500 and describes it as the response for failing to connect to a custom or Amazon Cognito authorizer. If you haven't customised it, it falls back to `DEFAULT_5XX`.

It helps to know the authorizer responses that look similar:

- **AUTHORIZER_CONFIGURATION_ERROR (500)**: API Gateway couldn't use the authorizer at all. That is this page.
- **AUTHORIZER_FAILURE (500)**: the authorizer ran but failed to authenticate the caller.
- **UNAUTHORIZED (401)**: the authorizer rejected the caller, or a required identity source was missing.
- **ACCESS_DENIED (403)**: the authorizer returned a policy with `Deny`.

If you're seeing 403, your authorizer is working and saying no. A 401 means it said no, or that the identity source was missing and API Gateway never called it. A 500 with this error type means it never produced a decision API Gateway could use. The [HTTP API authorizer docs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html) give the two triggers in one line: API Gateway can't invoke the authorizer, or the authorizer returns a response in an invalid format. Every cause below is one of those two.

```mermaid alt="Path of a request through a Lambda authorizer, showing the two places it turns into AuthorizerConfigurationException"
graph TB
A[Client calls a protected route] --> B{Identity source present?}
B -->|no| C[401 Unauthorized]
B -->|yes| D{Can API Gateway invoke the Lambda?}
D -->|no| E[500 AuthorizerConfigurationException]
D -->|yes| F{Response in the right format?}
F -->|no| E
F -->|yes| G{Policy allows the method?}
G -->|no| H[403 Access denied]
G -->|yes| I[Request reaches the backend]
style E fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d
style I fill:#d1fae5,stroke:#6ee7b7,color:#065f46
```

## Get the real reason from the logs first

The client only sees the 500. The detail is in API Gateway's logs, so turn them on before you guess.

- **REST APIs**: turn on execution logging for the stage. The [REST logging guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html) says execution logs record the actions API Gateway takes, including errors and execution traces. Logging needs an IAM role trusted by `apigateway.amazonaws.com` with the `AmazonAPIGatewayPushToCloudWatchLogs` managed policy, set as `cloudWatchRoleArn` on the account in each Region.
- **HTTP APIs**: turn on access logging and add `$context.authorizer.error` to the log format. The HTTP API docs recommend exactly this.

You can also call the authorizer directly from the CLI on a REST API:

```bash
aws apigateway test-invoke-authorizer \
  --rest-api-id 1234123412 \
  --authorizer-id efg1234 \
  --headers Authorization='Value'
```

The log line tells you which branch of the diagram you're on. A permission message points to cause 1. A message about the response body points to causes 2 to 4.

## Cause 1: API Gateway has no permission to invoke the authorizer function

This is a common cause, and it shows up after an authorizer or API has been recreated, after the function was replaced, or when the authorizer was built with the CLI, CloudFormation or Terraform. The console adds the permission for you: in the REST console, leaving **Lambda invoke role** blank makes it set a resource-based policy on the function. With the CLI, CloudFormation or Terraform, you have to grant the permission yourself, through a resource-based policy on the function or an IAM role on the authorizer.

The HTTP API docs are explicit: if API Gateway doesn't have permission to invoke your function, clients receive a 500 Internal Server Error.

### How to tell it's this

Check the function's resource policy for a statement that lets `apigateway.amazonaws.com` invoke it, scoped to your API:

```bash
aws lambda get-policy --function-name my-authorizer-function \
  --query Policy --output text | jq .
```

You want a statement with `"Principal": {"Service": "apigateway.amazonaws.com"}`, `"Action": "lambda:InvokeFunction"` and a `SourceArn` condition that matches your current API ID and authorizer ID. If the `SourceArn` names an old API ID, the permission belongs to an API that no longer exists.

Then check how the authorizer is set up to call the function:

```bash
aws apigateway get-authorizers --rest-api-id 1234123412
```

If `authorizerCredentials` holds a role ARN, API Gateway assumes that role to invoke the function. AWS says to leave the credentials unset when you want to use resource-based permissions on the function ([update-authorizer reference](https://docs.aws.amazon.com/cli/latest/reference/apigatewayv2/update-authorizer.html)). The role must trust `apigateway.amazonaws.com` and allow `lambda:InvokeFunction` on the function named in `authorizerUri`.

### Fix it

1. Add the resource-based permission, scoped to the authorizer:

```bash
aws lambda add-permission \
  --function-name my-authorizer-function \
  --statement-id apigateway-invoke-authorizer \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:us-west-2:123456789012:abcdef123/authorizers/def123"
```

2. If the authorizer uses an invoke role, fix the role's trust policy and its `lambda:InvokeFunction` permission. Or clear the role from the authorizer and rely on the resource-based permission.
3. In the console, a quick reset also works: open the authorizer, choose the function again with **Lambda invoke role** left blank, and save. The console writes the resource policy again.
4. Redeploy the REST API to the stage and call it again with curl.

In infrastructure code, declare the permission next to the authorizer so they're always created and destroyed together.

## Cause 2: the authorizer returns the wrong shape

API Gateway reads the authorizer's output as a structured object. The [REST authorizer output reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html) says it must include `principalId` and a `policyDocument` containing a list of policy statements. `context` is optional. Three versions of this mistake turn up again and again.

**Returning a JSON string.** In Python, `return json.dumps(policy)` hands API Gateway a string. API Gateway can't read a string as a policy object.

**Returning a proxy integration response.** Code copied from a Lambda proxy example returns `statusCode`, `headers` and `body`. That's the format for an integration. An authorizer has its own format.

**Missing or misspelt required keys.** No `principalId`, `PolicyDocument` with a capital P, or `Statement` as a single object where you meant a list.

### How to tell it's this

Your execution logs or `test-invoke-authorizer` output show the authorizer result body. If it starts with a quote, or contains `statusCode` and `body`, or has no `principalId`, this is your cause.

### Fix it

1. Return a native object. In Python:

```python
def lambda_handler(event, context):
    effect = "Allow" if is_valid(event["authorizationToken"]) else "Deny"
    return {
        "principalId": "user-123",
        "policyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "execute-api:Invoke",
                    "Effect": effect,
                    "Resource": event["methodArn"],
                }
            ],
        },
        "context": {"tenant": "acme"},
    }
```

2. If you build the policy as a string somewhere upstream, parse it before returning: `return json.loads(policy_string)`.
3. In Node.js, return the object from an async handler. Don't wrap it in `JSON.stringify` or a `statusCode` envelope.
4. If your API uses a usage plan with `apiKeySource` set to `AUTHORIZER`, the output reference says the function must also return one of the plan's API keys as `usageIdentifierKey`.

One more limit from the same reference: the `Resource` ARN in a policy statement is limited to 512 characters. Build it from `methodArn`, or use a wildcard such as `arn:aws:execute-api:region:account:api-id/stage/*`, and keep long tokens out of the path.

## Cause 3: the authorizer context holds an object or an array

This catches people who pass a decoded JWT through to the backend. A claims object almost always has an array somewhere, such as `roles` or `groups`. For REST API authorizers the output reference states that no key in `context` can take a JSON object or array as its value. Values must be strings, numbers or booleans, and API Gateway turns them all into strings.

### How to tell it's this

The policy looks right, the keys are right, and the result body in the logs shows a nested `{` or `[` inside `context`. Remove the `context` block, redeploy, and call again. If the 500 goes away, you've found it.

### Fix it

1. Flatten nested values before returning them:

```javascript
export const handler = async (event) => {
  const claims = await verify(event.authorizationToken);
  return {
    principalId: claims.sub,
    policyDocument: {
      Version: '2012-10-17',
      Statement: [{ Action: 'execute-api:Invoke', Effect: 'Allow', Resource: event.methodArn }],
    },
    context: {
      sub: claims.sub,
      roles: claims.roles.join(','),      // array to string
      profile: JSON.stringify(claims.profile) // object to string
    },
  };
};
```

2. Parse the values back in the backend. With Lambda proxy integration the values arrive at `event.requestContext.authorizer.<key>`. In mapping templates use `$context.authorizer.<key>`.
3. Or pass the original token through to the backend and decode it there.

HTTP APIs are different here. The HTTP API docs show 2.0 authorizers returning `arrayKey` and `mapKey` values in `context`. The flat-value restriction above is stated for REST API authorizers. For the 1.0 format the HTTP API docs also reserve `claims`, so you can't use it as the context object.

## Cause 4: an HTTP API authorizer doesn't match its payload format version

On HTTP APIs, `authorizerPayloadFormatVersion` decides both what API Gateway sends to your function and what it expects back. The [HTTP API authorizer docs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html) give the rules:

- **1.0** expects an IAM policy response (`principalId`, `policyDocument`, optional `context`). Use it when you need compatibility with REST APIs.
- **2.0** accepts an IAM policy, or a simple response `{"isAuthorized": true}` with optional `context`. The Boolean form only works if simple responses are turned on for the authorizer.

The console picks the latest version by default. The CLI, CloudFormation and SDKs make you set the version yourself. That's how one function gets attached with settings it wasn't written for.

### How to tell it's this

```bash
aws apigatewayv2 get-authorizers --api-id abcdef123
```

Compare `AuthorizerPayloadFormatVersion` and `EnableSimpleResponses` with what your code returns. Returning `isAuthorized` while simple responses are off, or with version 1.0, is a mismatch. So is reading `event.methodArn` in code attached as version 2.0: version 2.0 sends `routeArn`.

### Fix it

1. Pick one contract. For a Boolean decision, set version 2.0 and turn on simple responses. The [update-authorizer reference](https://docs.aws.amazon.com/cli/latest/reference/apigatewayv2/update-authorizer.html) lists both flags:

```bash
aws apigatewayv2 update-authorizer \
  --api-id abcdef123 \
  --authorizer-id def123 \
  --authorizer-payload-format-version 2.0 \
  --enable-simple-responses
```

2. Or keep the settings and change the function to return the matching shape, using `event.routeArn` for the policy resource under 2.0.
3. Call the route again and check that `$context.authorizer.error` is empty in the access log.

## Why the console test passes while real calls fail

This comes up a lot. The console test isn't the same as a real request. The [REST authorizer configuration guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/configure-api-gateway-lambda-authorization.html) gives one example: when the authorizer denies a request in the test environment, the test returns `200 OK`, while the same deny outside the test returns `403 Forbidden`. Treat a green console test as proof that your function runs. It doesn't prove the deployed stage can call it or read its output. Always confirm with curl or Postman against the stage URL, after a fresh deployment.

## Keeping authorizer configuration errors out of production

- **Declare the permission with the authorizer.** Put the `lambda:InvokeFunction` permission in the same template or module as the authorizer, scoped by `SourceArn`. A rebuilt API then gets a fresh permission automatically.
- **Unit test the output shape.** Assert that the handler returns an object, not a string, that `principalId` and `policyDocument.Statement` exist, and, for REST APIs, that every `context` value is a string, number or boolean.
- **Smoke test the deployed stage.** After each deploy, call one protected route with a valid token and one with an invalid token. Expect a 2xx and a 401 or 403. A 500 fails the pipeline.
- **Keep the logs on.** Leave execution logging (REST) or `$context.authorizer.error` in the access log format (HTTP) turned on in production, so the next 500 explains itself.
- **Alarm on 5xx responses.** A broken authorizer fails every request on the routes it protects, so a sudden jump in the API's 5xx count right after a deploy is the signal to watch for.
- **Give it its own response.** Customise the `AUTHORIZER_CONFIGURATION_ERROR` gateway response with a distinct message, so clients and dashboards can tell an authorizer problem apart from a backend 500.

## Tracing an authorizer 500 back to the deploy with Polylane

When this error appears straight after a release, the useful question is what changed. Polylane connects to AWS, so when authorizer 500s start after a deploy it works out why, and the fix arrives ready for your team's normal review.

Running on AWS? See [how Polylane monitors AWS in production](https://polylane.com/for/aws/).

## Common questions

**What HTTP status does AuthorizerConfigurationException return?**

It returns 500. It's the AUTHORIZER_CONFIGURATION_ERROR gateway response, whose default status is 500 and which falls back to DEFAULT_5XX if you haven't customised it. A 403 means the authorizer worked and rejected the caller. A 401 can mean the same, or that the identity source was missing and API Gateway never invoked the authorizer.

**Why does the API Gateway console test work but curl gets a 500?**

The console test doesn't behave like a deployed stage. AWS documents that a deny in the test environment returns 200 OK, while the same deny on the stage returns 403. A passing console test doesn't prove the deployed stage behaves the same, so always test against the stage URL after redeploying.

**How do I give API Gateway permission to invoke my Lambda authorizer?**

Run aws lambda add-permission with --principal apigateway.amazonaws.com, --action lambda:InvokeFunction and a --source-arn of the form arn:aws:execute-api:region:account:api-id/authorizers/authorizer-id. Or set an invoke role on the authorizer that trusts apigateway.amazonaws.com and allows lambda:InvokeFunction on the function.

**Can I return an array or object in the authorizer context?**

Not on REST APIs. AWS states that context values can't be JSON objects or arrays, so join arrays into strings or JSON.stringify objects and parse them in the backend. HTTP API 2.0 authorizer examples do show array and map values in context.

**What fields must a Lambda authorizer return?**

For REST APIs and HTTP API format 1.0: principalId and a policyDocument with a Statement list. context is optional. usageIdentifierKey is required when the API's apiKeySource is AUTHORIZER. HTTP API format 2.0 with simple responses turned on can return isAuthorized as a Boolean.

**Where do I see the real cause of AuthorizerConfigurationException?**

On REST APIs, turn on execution logging for the stage. It records errors and execution traces. On HTTP APIs, add $context.authorizer.error to the access log format. You can also run aws apigateway test-invoke-authorizer against a REST authorizer.

**Does a Python authorizer that returns json.dumps cause this error?**

Yes. json.dumps returns a string, and API Gateway expects an object with principalId and policyDocument. Return the dict directly, or call json.loads on a policy string before returning it.

## Sources

- [Output from an API Gateway Lambda authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html)
- [Gateway response types for API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/supported-gateway-response-types.html)
- [Control access to HTTP APIs with AWS Lambda authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html)
- [Configure an API Gateway Lambda authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/configure-api-gateway-lambda-authorization.html)
- [Set up CloudWatch logging for REST APIs in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html)
- [AWS CLI reference: apigatewayv2 update-authorizer](https://docs.aws.amazon.com/cli/latest/reference/apigatewayv2/update-authorizer.html)
- [Polylane docs](https://docs.polylane.com/llms-full.txt)

## About the author

Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.

## Related

- [How to correlate incidents with deploys using evidence](https://polylane.com/learn/deployment-safety/how-to-correlate-incidents-with-deploys/): Learn how to correlate incidents with deploys using release markers, metrics, logs and verified GitHub, Vercel, Cloudflare and Kubernetes change events.
- [Fix 403 Forbidden (Access Denied) from Amazon S3](https://polylane.com/learn/troubleshooting/aws-403-forbidden/): Why Amazon S3 returns 403 Forbidden: Block Public Access, missing s3:ListBucket, object ownership, clock skew, expired presigned URLs and CloudFront. Fix each.
- [Fix 502 Bad Gateway on AWS API Gateway and Load Balancers](https://polylane.com/learn/troubleshooting/aws-502-bad-gateway/): Why AWS returns 502 Bad Gateway from API Gateway or an Application Load Balancer, how to find which hop wrote it, and the fix for each cause.
- [Fix “The bucket does not allow ACLs” in Amazon S3](https://polylane.com/learn/troubleshooting/aws-the-bucket-does-not-allow-acls/): Why S3 returns AccessControlListNotSupported: The bucket does not allow ACLs, how to confirm it, and how to fix it by dropping the ACL or re-enabling ACLs.
- [AsyncEventsDropped: diagnose Lambda async event loss](https://polylane.com/learn/troubleshooting/AsyncEventsDropped/): AsyncEventsDropped counts Lambda async events discarded after age or retry limits. Check throttling, destinations and DLQs, then verify processing recovery.

Get started with one command: `curl -fsSL https://polylane.com/setup | bash` installs the CLI, connects your coding agents, and creates the account.
