Get started Dashboard
Troubleshooting ·

Fix AuthorizerConfigurationException in API Gateway

Explore with AI

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.

Ongoing incident? Our founding team can help you get your AWS app back up.
On this page

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 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 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.

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 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:

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:

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:

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). 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:
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"
  1. 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.
  2. 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.
  3. 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 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:
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"},
    }
  1. If you build the policy as a string somewhere upstream, parse it before returning: return json.loads(policy_string).
  2. In Node.js, return the object from an async handler. Don’t wrap it in JSON.stringify or a statusCode envelope.
  3. 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:
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
    },
  };
};
  1. 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>.
  2. 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 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

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 lists both flags:
aws apigatewayv2 update-authorizer \
  --api-id abcdef123 \
  --authorizer-id def123 \
  --authorizer-payload-format-version 2.0 \
  --enable-simple-responses
  1. Or keep the settings and change the function to return the matching shape, using event.routeArn for the policy resource under 2.0.
  2. 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 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.

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 (7)

  1. Output from an API Gateway Lambda authorizer
  2. Gateway response types for API Gateway
  3. Control access to HTTP APIs with AWS Lambda authorizers
  4. Configure an API Gateway Lambda authorizer
  5. Set up CloudWatch logging for REST APIs in API Gateway
  6. AWS CLI reference: apigatewayv2 update-authorizer
  7. Polylane docs

About the author

Boris Tane

Founder of Polylane

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

Nobody should be on-call. Polylane watches your infra, finds what broke, and writes the fix.

Get started for free