API Gateway Authorization Strategies

Implement robust API authorization using Cognito, Lambda authorizers, and IAM policies for secure API access control.

Securing APIs requires robust authorization mechanisms. AWS API Gateway supports multiple authorization strategies to protect your endpoints while maintaining flexibility.

Cognito User Pool Authorizer

Setup Configuration

CognitoAuthorizer:
  Type: AWS::ApiGateway::Authorizer
  Properties:
    Name: CognitoAuthorizer
    RestApiId: !Ref ApiGateway
    Type: COGNITO_USER_POOLS
    IdentitySource: method.request.header.Authorization
    ProviderARNs:
      - !GetAtt CognitoUserPool.Arn

ProtectedMethod:
  Type: AWS::ApiGateway::Method
  Properties:
    RestApiId: !Ref ApiGateway
    ResourceId: !Ref ProtectedResource
    HttpMethod: GET
    AuthorizationType: COGNITO_USER_POOLS
    AuthorizerId: !Ref CognitoAuthorizer
    AuthorizationScopes:
      - api/read
      - api/write

Token Validation

import jwt
import requests
from functools import lru_cache

@lru_cache(maxsize=1)
def get_cognito_keys(region, user_pool_id):
    keys_url = f'https://cognito-idp.{region}.amazonaws.com/{user_pool_id}/.well-known/jwks.json'
    response = requests.get(keys_url)
    return response.json()['keys']

def validate_token(token, region, user_pool_id, client_id):
    keys = get_cognito_keys(region, user_pool_id)
    
    headers = jwt.get_unverified_header(token)
    key = next((k for k in keys if k['kid'] == headers['kid']), None)
    
    if not key:
        raise ValueError('Invalid token key')
    
    public_key = jwt.algorithms.RSAAlgorithm.from_jwk(key)
    
    return jwt.decode(
        token,
        public_key,
        algorithms=['RS256'],
        audience=client_id,
        issuer=f'https://cognito-idp.{region}.amazonaws.com/{user_pool_id}'
    )

Lambda Authorizer

Token-Based Authorizer

import json
import jwt
import os

def lambda_handler(event, context):
    token = event.get('authorizationToken', '').replace('Bearer ', '')
    
    try:
        claims = validate_jwt(token)
        
        return generate_policy(
            claims['sub'],
            'Allow',
            event['methodArn'],
            context={
                'userId': claims['sub'],
                'email': claims.get('email'),
                'roles': claims.get('custom:roles', '')
            }
        )
    except Exception as e:
        print(f"Authorization failed: {e}")
        return generate_policy('user', 'Deny', event['methodArn'])

def generate_policy(principal_id, effect, resource, context=None):
    policy = {
        'principalId': principal_id,
        'policyDocument': {
            'Version': '2012-10-17',
            'Statement': [{
                'Action': 'execute-api:Invoke',
                'Effect': effect,
                'Resource': resource
            }]
        }
    }
    
    if context:
        policy['context'] = context
    
    return policy

Request-Based Authorizer

def request_authorizer(event, context):
    headers = event.get('headers', {})
    query_params = event.get('queryStringParameters', {})
    
    api_key = headers.get('x-api-key')
    client_id = query_params.get('client_id')
    
    if not api_key or not validate_api_key(api_key, client_id):
        return generate_policy('unknown', 'Deny', event['methodArn'])
    
    client_config = get_client_config(client_id)
    
    return generate_policy(
        client_id,
        'Allow',
        build_resource_arn(event, client_config['allowed_resources']),
        context={
            'clientId': client_id,
            'tier': client_config['tier'],
            'rateLimit': str(client_config['rate_limit'])
        }
    )

IAM Authorization

Resource Policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/BackendServiceRole"
      },
      "Action": "execute-api:Invoke",
      "Resource": "arn:aws:execute-api:us-east-1:123456789012:api-id/prod/GET/*"
    },
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "execute-api:Invoke",
      "Resource": "arn:aws:execute-api:us-east-1:123456789012:api-id/prod/*",
      "Condition": {
        "NotIpAddress": {
          "aws:SourceIp": ["10.0.0.0/8", "192.168.0.0/16"]
        }
      }
    }
  ]
}

SigV4 Signing

import boto3
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
import requests

def call_iam_protected_api(url, method='GET', data=None):
    session = boto3.Session()
    credentials = session.get_credentials()
    
    request = AWSRequest(method=method, url=url, data=data)
    SigV4Auth(credentials, 'execute-api', 'us-east-1').add_auth(request)
    
    response = requests.request(
        method=method,
        url=url,
        headers=dict(request.headers),
        data=data
    )
    
    return response.json()

Authorization Caching

LambdaAuthorizer:
  Type: AWS::ApiGateway::Authorizer
  Properties:
    Name: CachedLambdaAuthorizer
    RestApiId: !Ref ApiGateway
    Type: TOKEN
    AuthorizerUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AuthorizerFunction.Arn}/invocations
    AuthorizerResultTtlInSeconds: 300
    IdentitySource: method.request.header.Authorization
    IdentityValidationExpression: ^Bearer [-a-zA-Z0-9._~+/]+=*$

Working with Warqline

We are a cloud engineering consultancy and an official AWS and Google Cloud partner. If you are running this in production and want a second pair of eyes, we scope work in a free 45-minute technical call: you describe what you are running and what worries you, and we tell you what we would look at first.

Talk to an engineer

Conclusion

Effective API authorization requires selecting the right strategy for your use case. Combine Cognito for user authentication, Lambda authorizers for custom logic, and IAM for service-to-service communication.