APIs at scale on AWS are less about the APIs themselves and more about managing the flow and security of data across a distributed system.

Let’s look at API Gateway, Lambda, and Cognito working together for a common scenario: a mobile app needing to access user-specific data securely.

Imagine a GET /users/{userId}/profile request.

Here’s how it might flow, with actual AWS config snippets:

1. The Request Arrives at API Gateway

// Example API Gateway Method Request Configuration for GET /users/{userId}/profile
{
  "httpMethod": "GET",
  "resourceId": "abcdef123",
  "authorizationType": "AWS_COGNITO",
  "authorizerId": "xyz789",
  "requestParameters": {
    "method.request.path.userId": true
  },
  "requestModels": {},
  "requestValidatorId": "ghijkl456"
}

API Gateway is the front door. It receives the HTTP request, checks if the userId path parameter is present, and crucially, it initiates authorization. In this case, it’s AWS_COGNITO, meaning it expects a JWT from your Cognito user pool.

2. Cognito Handles Authentication

Before the request even hits API Gateway, your mobile app would have handled user login via Cognito. This process issues a JWT (JSON Web Token) to the app. This token contains claims about the authenticated user, like their sub (subject, a unique user ID) and cognito:username.

When the app makes the API call, it includes this JWT in the Authorization header:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

API Gateway’s authorizerId (xyz789) points to a Cognito User Pool Authorizer. This authorizer intercepts the request, validates the JWT’s signature against Cognito’s public keys, checks its expiration, and ensures it’s for the correct issuer (iss). If valid, it passes the validated claims to the backend.

3. Rate Limiting and Throttling

This is where you prevent abuse and ensure stability. API Gateway allows you to set usage plans and API keys.

// Example API Gateway Usage Plan Configuration
{
  "usagePlanId": "uvw0123",
  "name": "MobileAppHighUsagePlan",
  "description": "Plan for heavy mobile app traffic",
  "throttle": {
    "burstLimit": 100, // Max requests per second allowed
    "rateLimit": 50   // Average requests per second allowed
  },
  "quota": {
    "limit": 10000, // Max requests per day
    "period": "DAY"
  },
  "apiStages": [
    {
      "apiId": "api-id-123",
      "stage": "prod"
    }
  ],
  "tags": {
    "environment": "production"
  }
}

You associate this uvw0123 usage plan with your API stage. Then, you generate an API key and associate that key with the usage plan. The mobile app includes this API key in a custom header (e.g., x-api-key). API Gateway checks the key against the usage plan’s limits. If the rateLimit of 50 requests per second is exceeded, API Gateway returns a 429 Too Many Requests error before it even hits your backend.

4. The Backend: Lambda Function

API Gateway then invokes your Lambda function, passing along the request details and the authorized user’s claims from Cognito.

# Example Lambda function handler (Python)
import json
import boto3
from botocore.exceptions import ClientError

dynamodb = boto3.resource('dynamodb')
users_table = dynamodb.Table('UsersProfileTable') # Your DynamoDB table name

def lambda_handler(event, context):
    print("Received event: " + json.dumps(event, indent=2))

    # Extracting userId from path parameters
    user_id = event['pathParameters']['userId']

    # Extracting claims from Cognito authorizer context
    cognito_claims = event['requestContext']['authorizer']['claims']
    authenticated_user_id = cognito_claims.get('sub')

    # Security check: Ensure the authenticated user can only access their own profile
    if user_id != authenticated_user_id:
        return {
            'statusCode': 403,
            'body': json.dumps('Forbidden: You can only access your own profile.')
        }

    try:
        response = users_table.get_item(
            Key={
                'userId': user_id
            }
        )
        item = response.get('Item')
        if not item:
            return {
                'statusCode': 404,
                'body': json.dumps(f'User profile not found for {user_id}')
            }
        return {
            'statusCode': 200,
            'body': json.dumps(item)
        }
    except ClientError as e:
        print(e.response['Error']['Message'])
        return {
            'statusCode': 500,
            'body': json.dumps('Internal server error')
        }

Notice how the Lambda function uses event['requestContext']['authorizer']['claims']['sub'] to get the actual authenticated user ID. It then explicitly checks if this matches the userId from the path. This is a critical authorization step, ensuring that even if the client tries to request another user’s data, the backend rejects it based on the authenticated identity.

The Mental Model:

  • API Gateway: The public-facing receptionist. Handles incoming traffic, checks credentials (API keys, JWTs), enforces basic rules (rate limits), and directs requests to the right internal department.
  • Cognito: The HR and Security department. Manages user identities, authenticates them, and issues verifiable credentials (JWTs).
  • Lambda: The specialized worker. Performs the actual business logic, interacts with data stores, and enforces fine-grained authorization based on the identity information provided by the receptionist.
  • DynamoDB/RDS/etc.: The filing cabinets and vaults. Stores the data that the worker processes.

The most surprising aspect is how much of the security and access control logic is offloaded from your core application code. API Gateway and Cognito handle the heavy lifting of authentication (proving who you are) and coarse-grained authorization (whether you’re allowed to try). Your Lambda function then focuses on the fine-grained authorization (can this specific authenticated user access this specific piece of data?). This separation of concerns is what allows you to scale.

The next logical step in managing this system at scale is implementing robust logging and monitoring, specifically using AWS CloudWatch Logs and Metrics to track API Gateway access, Lambda execution times, and error rates.

Want structured learning?

Take the full Cloud Computing course →