Ongoing service disruptions
For the most recent update on ongoing service disruptions affecting the AWS Middle East (UAE) Region (ME-CENTRAL-1), refer to the AWS Health Dashboard. For information on AWS Service migration, see How do I migrate my services to another region?
How to Authenticate and Authorize Applications with Different Permissions Using Amazon Cognito M2M
Implement secure machine-to-machine authentication with differentiated access permissions using Amazon Cognito and API Gateway.
The Challenge
Microservices and automated systems often need different levels of access to the same backend APIs. For example, an Order Processing Service requires full read/write access to customer data, while a Reporting Service needs only read access for analytics. Traditional approaches like shared API keys don't provide the granular permission control needed for these machine-to-machine scenarios, leading to security risks and compliance violations.
Architecture Solution
API Gateway Cognito Authorizer with Scope-Based Authorization
Amazon Cognito M2M authentication using OAuth 2.0 Client Credentials Grant combined with API Gateway Cognito Authorizer provides centralized authorization for machine-to-machine communication. This approach validates JWT tokens and enforces scope-based permissions at the API Gateway layer, simplifying backend implementation.
Example Scenario: Two microservices with different permissions
- Order Processing Service: Full access (
customer-api/admin,customer-api/write,customer-api/read) - Reporting Service: Read-only access (
customer-api/read)
Architecture Diagram
This is how it works: Each microservice authenticates with Cognito using unique client credentials and receives a JWT access token containing specific scopes. API Gateway validates the token signature and enforces permissions based on the scopes, only forwarding authorized requests to the backend.
Backend Lambda Implementation
With API Gateway handling authorization, the backend Lambda is simplified:
import json def lambda_handler(event, context): # API Gateway has already validated JWT and scopes # No JWT validation code needed in backend http_method = event['httpMethod'] path = event['path'] if http_method == 'GET' and path == '/customers': # Both admin and employee can access (both have read scope) return { 'statusCode': 200, 'body': json.dumps({'customers': get_customer_list()}) } elif http_method == 'DELETE' and '/customers/' in path: # Only admin can access (API Gateway enforces admin scope) customer_id = path.split('/')[-1] delete_customer_record(customer_id) return { 'statusCode': 200, 'body': json.dumps({'message': f'Customer {customer_id} deleted'}) } return { 'statusCode': 404, 'body': json.dumps({'error': 'Not found'}) } def get_customer_list(): return [{'id': '123', 'name': 'John Doe'}, {'id': '456', 'name': 'Jane Smith'}] def delete_customer_record(customer_id): # Simulate customer deletion print(f"Deleting customer {customer_id}")
Deployment Process
Deploy the complete Infrastructure as Code template provided in the Appendix. The CloudFormation template creates:
- Cognito User Pool with OAuth domain
- Resource Server with custom scopes (
read,write,admin) - Two Application Clients with different permission levels
- API Gateway with Cognito Authorizer
- Lambda function for backend processing
- Method-level scope enforcement
Step 1: Save the CloudFormation Template
Copy the complete CloudFormation template from the Appendix section at the end of this article and save it to a new file:
# Create a new file for the template nano cognito-m2m-demo.yaml # Or use your preferred text editor code cognito-m2m-demo.yaml vim cognito-m2m-demo.yaml
Paste the entire CloudFormation template content from the Appendix into this file and save it.
Step 2: Deploy the Stack
Run the following AWS CLI command to deploy the complete solution:
aws cloudformation create-stack \ --stack-name cognito-m2m-demo \ --template-body file://cognito-m2m-demo.yaml \ --capabilities CAPABILITY_IAM \ --region us-east-1
The outputs will provide:
- TokenEndpoint: OAuth2 token endpoint URL
- OrderClientId: Order Processing Service client ID
- ReportingClientId: Reporting Service client ID
- ApiUrl: API Gateway endpoint URL
- QuickTestCommands: Ready-to-use test commands
Testing and Validation
Test 1: Order Processing Service Gets Full Access Token
# Get stack outputs TOKEN_ENDPOINT=$(aws cloudformation describe-stacks --stack-name cognito-m2m-demo --query 'Stacks[0].Outputs[?OutputKey==`TokenEndpoint`].OutputValue' --output text) ORDER_CLIENT_ID=$(aws cloudformation describe-stacks --stack-name cognito-m2m-demo --query 'Stacks[0].Outputs[?OutputKey==`OrderClientId`].OutputValue' --output text) API_URL=$(aws cloudformation describe-stacks --stack-name cognito-m2m-demo --query 'Stacks[0].Outputs[?OutputKey==`ApiUrl`].OutputValue' --output text) # Get client secret from Cognito console, then request token CREDENTIALS=$(echo -n "$ORDER_CLIENT_ID:$ORDER_CLIENT_SECRET" | base64) ORDER_TOKEN=$(curl -s -X POST "$TOKEN_ENDPOINT" \ -H "Authorization: Basic $CREDENTIALS" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&scope=customer-api/read customer-api/write customer-api/admin" \ | jq -r '.access_token') echo "Order Processing Service token obtained: ${ORDER_TOKEN:0:50}..."
Test 2: Reporting Service Gets Read-Only Token
REPORTING_CLIENT_ID=$(aws cloudformation describe-stacks --stack-name cognito-m2m-demo --query 'Stacks[0].Outputs[?OutputKey==`ReportingClientId`].OutputValue' --output text) CREDENTIALS=$(echo -n "$REPORTING_CLIENT_ID:$REPORTING_CLIENT_SECRET" | base64) REPORTING_TOKEN=$(curl -s -X POST "$TOKEN_ENDPOINT" \ -H "Authorization: Basic $CREDENTIALS" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&scope=customer-api/read" \ | jq -r '.access_token') echo "Reporting Service token obtained: ${REPORTING_TOKEN:0:50}..."
Test 3: API Gateway Enforces Scope-Based Authorization
# Order Processing Service can delete customers (has admin scope) curl -X DELETE "$API_URL/customers/123" \ -H "Authorization: Bearer $ORDER_TOKEN" # Expected: 200 OK - Customer deleted (API Gateway allows, Lambda processes) # Reporting Service cannot delete customers (lacks admin scope) curl -X DELETE "$API_URL/customers/123" \ -H "Authorization: Bearer $REPORTING_TOKEN" # Expected: 403 Forbidden (API Gateway blocks, never reaches Lambda) { "message": "Insufficient privileges to access this resource" } # Reporting Service can read customer data (has read scope) curl -X GET "$API_URL/customers" \ -H "Authorization: Bearer $REPORTING_TOKEN" # Expected: 200 OK - Customer data returned (API Gateway allows, Lambda processes) { "customers": [ {"id": "123", "name": "John Doe"}, {"id": "456", "name": "Jane Smith"} ] }
This demonstrates that API Gateway validates JWT tokens and enforces scope-based permissions for machine-to-machine communication before requests reach the backend Lambda function.
Security Benefits
API Gateway Cognito Authorizer provides:
- AWS-Managed Security: No custom JWT validation code needed
- Centralized Authorization: All permission checks at API Gateway layer
- Cryptographic Protection: RSA-256 signatures prevent token tampering
- Time-bound Security: 1-hour automatic token expiration
- Performance Optimization: Built-in authorizer result caching
- Simplified Backend: Lambda focuses on business logic only
JWT Token Security: Any attempt to modify token scopes breaks the cryptographic signature, immediately detected by API Gateway's built-in validation.
Considerations: When API Gateway API Keys Are Sufficient
API Gateway API Keys are simpler and sufficient when all applications need identical access to the same resources.
Use API Gateway API Keys When:
1. Uniform Data Access
All applications access the same data with identical permissions:
# Backend Lambda - API Gateway already validated API key def lambda_handler(event, context): # No authorization code needed - all clients get same data return { 'statusCode': 200, 'body': json.dumps(get_all_system_metrics()) }
2. Rate Limiting and Client Identification
You need to identify and limit different clients but they all get the same access:
# API Gateway Method - API Key required, same backend access GetMetricsMethod: Type: AWS::ApiGateway::Method Properties: ApiKeyRequired: true # API Gateway validates key # Same Lambda backend for all clients
Use Cognito M2M When:
1. Different Permission Levels
Applications need different levels of access to the same resources:
# Backend Lambda - API Gateway already validated JWT scopes def lambda_handler(event, context): # API Gateway passes different requests based on scopes if event['httpMethod'] == 'GET': return get_orders() # Both admin and employee reach here elif event['httpMethod'] == 'DELETE': return delete_order() # Only admin reaches here (API Gateway filtered)
Real-World Examples:
| API Keys Sufficient | Cognito M2M Required |
|---|---|
| Multiple monitoring tools accessing same metrics | Order service (full CRUD) vs Analytics (read-only) |
| Public API with usage tiers (same data, different limits) | SaaS platform with tenant-specific permissions |
| Partner integrations with identical data access permission | Financial: trading vs reporting vs compliance systems |
Quick Assessment:
- Do different apps need different access levels? → Cognito M2M
- Just need to identify and rate-limit clients? → API Keys
- All apps access same data identically? → API Keys
Conclusion
Amazon Cognito M2M authentication with API Gateway Cognito Authorizer solves microservice authorization challenges by providing OAuth 2.0 scopes for fine-grained permissions, AWS-managed JWT token validation at the API Gateway layer, and simplified backend implementation. The solution includes centralized authorization enforcement, detailed audit trails, and meets industry compliance standards.
Key Achievement: Order Processing Service gets full access while Reporting Service gets read-only access, with API Gateway enforcing cryptographic token validation and scope-based authorization before requests reach the backend. The approach transforms complex machine-to-machine authorization requirements into a manageable, secure, and scalable system using AWS-managed services.
Appendix: Complete Infrastructure Code
AWSTemplateFormatVersion: '2010-09-09' Description: 'Application Authentication with Cognito M2M and API Gateway Authorizer' Parameters: ProjectName: Type: String Default: 'cognito-m2m-demo' Description: 'Project name for resource naming' AllowedPattern: '^[a-z0-9-]+$' ConstraintDescription: 'Must contain only lowercase letters, numbers, and hyphens' Resources: # Cognito User Pool UserPool: Type: AWS::Cognito::UserPool Properties: UserPoolName: !Sub '${ProjectName}-user-pool' UserPoolTags: Purpose: M2M-Demo Project: !Ref ProjectName # User Pool Domain (Fixed with compliant domain name) UserPoolDomain: Type: AWS::Cognito::UserPoolDomain Properties: Domain: !Sub 'm2m-demo-${AWS::AccountId}' UserPoolId: !Ref UserPool # Resource Server with Scopes ResourceServer: Type: AWS::Cognito::UserPoolResourceServer Properties: UserPoolId: !Ref UserPool Identifier: 'customer-api' Name: 'Customer API' Scopes: - ScopeName: 'read' ScopeDescription: 'Read access' - ScopeName: 'write' ScopeDescription: 'Write access' - ScopeName: 'admin' ScopeDescription: 'Admin access' # Order Processing Service Client (Full Permissions) OrderClient: Type: AWS::Cognito::UserPoolClient DependsOn: ResourceServer Properties: UserPoolId: !Ref UserPool ClientName: 'OrderProcessingService' GenerateSecret: true AllowedOAuthFlows: [client_credentials] AllowedOAuthScopes: ['customer-api/read', 'customer-api/write', 'customer-api/admin'] AllowedOAuthFlowsUserPoolClient: true # Reporting Service Client (Read-Only) ReportingClient: Type: AWS::Cognito::UserPoolClient DependsOn: ResourceServer Properties: UserPoolId: !Ref UserPool ClientName: 'ReportingService' GenerateSecret: true AllowedOAuthFlows: [client_credentials] AllowedOAuthScopes: ['customer-api/read'] AllowedOAuthFlowsUserPoolClient: true # Lambda Execution Role with enhanced security LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Condition: StringEquals: 'aws:SourceAccount': !Ref 'AWS::AccountId' ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Tags: - Key: Purpose Value: M2M-Demo - Key: Project Value: !Ref ProjectName # Backend Lambda Function with updated runtime BackendLambda: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${ProjectName}-backend' Runtime: python3.12 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 30 MemorySize: 128 Description: 'Backend API for Cognito M2M demo' Tags: - Key: Purpose Value: M2M-Demo - Key: Project Value: !Ref ProjectName Code: ZipFile: | import json import logging # Configure logging logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context): try: logger.info(f"Received event: {json.dumps(event)}") http_method = event.get('httpMethod', '') path = event.get('path', '') # Handle GET /customers if http_method == 'GET' and path == '/customers': return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps({ 'customers': [ {'id': '123', 'name': 'John Doe', 'email': 'john@example.com'}, {'id': '456', 'name': 'Jane Smith', 'email': 'jane@example.com'} ], 'message': 'Successfully retrieved customers' }) } # Handle DELETE /customers/{id} elif http_method == 'DELETE' and '/customers/' in path: customer_id = path.split('/')[-1] logger.info(f"Deleting customer: {customer_id}") return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps({ 'message': f'Customer {customer_id} deleted successfully', 'customerId': customer_id }) } # Handle unsupported methods/paths else: return { 'statusCode': 404, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps({ 'error': 'Not found', 'message': f'Method {http_method} not supported for path {path}' }) } except Exception as e: logger.error(f"Error processing request: {str(e)}") return { 'statusCode': 500, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps({ 'error': 'Internal server error', 'message': 'An error occurred processing your request' }) } # API Gateway with enhanced configuration ApiGateway: Type: AWS::ApiGateway::RestApi Properties: Name: !Sub '${ProjectName}-api' Description: 'API with Cognito M2M Authorization' EndpointConfiguration: Types: - REGIONAL Tags: - Key: Purpose Value: M2M-Demo - Key: Project Value: !Ref ProjectName # Cognito Authorizer with enhanced configuration CognitoAuthorizer: Type: AWS::ApiGateway::Authorizer Properties: Name: !Sub '${ProjectName}-cognito-authorizer' RestApiId: !Ref ApiGateway Type: COGNITO_USER_POOLS ProviderARNs: - !GetAtt UserPool.Arn IdentitySource: method.request.header.Authorization AuthorizerResultTtlInSeconds: 300 # Customers Resource CustomersResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref ApiGateway ParentId: !GetAtt ApiGateway.RootResourceId PathPart: 'customers' # Customer ID Resource CustomerIdResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref ApiGateway ParentId: !Ref CustomersResource PathPart: '{id}' # GET /customers Method (Read scope required) GetCustomersMethod: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref ApiGateway ResourceId: !Ref CustomersResource HttpMethod: GET AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref CognitoAuthorizer AuthorizationScopes: - 'customer-api/read' Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${BackendLambda.Arn}/invocations' # DELETE /customers/{id} Method (Admin scope required) DeleteCustomerMethod: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref ApiGateway ResourceId: !Ref CustomerIdResource HttpMethod: DELETE AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref CognitoAuthorizer AuthorizationScopes: - 'customer-api/admin' Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${BackendLambda.Arn}/invocations' # Lambda Permission for API Gateway LambdaApiGatewayPermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref BackendLambda Action: lambda:InvokeFunction Principal: apigateway.amazonaws.com SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGateway}/*/*' # API Deployment with proper dependencies ApiDeployment: Type: AWS::ApiGateway::Deployment DependsOn: - GetCustomersMethod - DeleteCustomerMethod Properties: RestApiId: !Ref ApiGateway StageName: 'prod' Outputs: UserPoolId: Description: 'Cognito User Pool ID' Value: !Ref UserPool Export: Name: !Sub '${AWS::StackName}-UserPoolId' UserPoolArn: Description: 'Cognito User Pool ARN' Value: !GetAtt UserPool.Arn Export: Name: !Sub '${AWS::StackName}-UserPoolArn' UserPoolDomain: Description: 'Cognito User Pool Domain' Value: !Ref UserPoolDomain Export: Name: !Sub '${AWS::StackName}-UserPoolDomain' TokenEndpoint: Description: 'OAuth2 Token Endpoint for M2M authentication' Value: !Sub 'https://m2m-demo-${AWS::AccountId}.auth.${AWS::Region}.amazoncognito.com/oauth2/token' Export: Name: !Sub '${AWS::StackName}-TokenEndpoint' OrderClientId: Description: 'Order Processing Service Client ID (Full permissions: read, write, admin)' Value: !Ref OrderClient Export: Name: !Sub '${AWS::StackName}-OrderClientId' ReportingClientId: Description: 'Reporting Service Client ID (Read-only permissions)' Value: !Ref ReportingClient Export: Name: !Sub '${AWS::StackName}-ReportingClientId' ApiUrl: Description: 'API Gateway URL for testing endpoints' Value: !Sub 'https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod' Export: Name: !Sub '${AWS::StackName}-ApiUrl' JWKSEndpoint: Description: 'JWKS Endpoint for Token Validation' Value: !Sub 'https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}/.well-known/jwks.json' Export: Name: !Sub '${AWS::StackName}-JWKSEndpoint' TestEndpoints: Description: 'Test endpoints for validation' Value: !Sub | GET (read scope): https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod/customers DELETE (admin scope): https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod/customers/{id} QuickTestCommands: Description: 'Ready-to-use test commands (get client secrets first)' Value: !Sub | # Step 1: Get client secrets aws cognito-idp describe-user-pool-client --user-pool-id ${UserPool} --client-id ${OrderClient} --query 'UserPoolClient.ClientSecret' --output text aws cognito-idp describe-user-pool-client --user-pool-id ${UserPool} --client-id ${ReportingClient} --query 'UserPoolClient.ClientSecret' --output text # Step 2: Test Order Service (replace CLIENT_SECRET) curl -X POST https://m2m-demo-${AWS::AccountId}.auth.${AWS::Region}.amazoncognito.com/oauth2/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=${OrderClient}&client_secret=CLIENT_SECRET&scope=customer-api/read customer-api/write customer-api/admin" # Step 3: Test API endpoints (replace ACCESS_TOKEN) curl -X GET https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod/customers -H "Authorization: Bearer ACCESS_TOKEN" curl -X DELETE https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod/customers/123 -H "Authorization: Bearer ACCESS_TOKEN" DeploymentValidation: Description: 'Validation checklist for successful deployment' Value: !Sub | ✅ User Pool Created: ${UserPool} ✅ Domain Available: https://m2m-demo-${AWS::AccountId}.auth.${AWS::Region}.amazoncognito.com ✅ API Gateway Deployed: https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod ✅ Lambda Function Ready: ${BackendLambda} ✅ Two Clients Configured: Order (full access) + Reporting (read-only) ✅ Ready for Testing: Use QuickTestCommands output above # Resource Server with Scopes ResourceServer: Type: AWS::Cognito::UserPoolResourceServer Properties: UserPoolId: !Ref UserPool Identifier: 'customer-api' Name: 'Customer API' Scopes: - ScopeName: 'read' ScopeDescription: 'Read access' - ScopeName: 'write' ScopeDescription: 'Write access' - ScopeName: 'admin' ScopeDescription: 'Admin access' # Order Processing Service Client (Full Permissions) OrderClient: Type: AWS::Cognito::UserPoolClient DependsOn: ResourceServer Properties: UserPoolId: !Ref UserPool ClientName: 'OrderProcessingService' GenerateSecret: true AllowedOAuthFlows: [client_credentials] AllowedOAuthScopes: ['customer-api/read', 'customer-api/write', 'customer-api/admin'] AllowedOAuthFlowsUserPoolClient: true # Reporting Service Client (Read-Only) ReportingClient: Type: AWS::Cognito::UserPoolClient DependsOn: ResourceServer Properties: UserPoolId: !Ref UserPool ClientName: 'ReportingService' GenerateSecret: true AllowedOAuthFlows: [client_credentials] AllowedOAuthScopes: ['customer-api/read'] AllowedOAuthFlowsUserPoolClient: true # Lambda Execution Role with enhanced security LambdaExecutionRole: Type: AWS::IAM::Role Properties: RoleName: !Sub '${ProjectName}-lambda-execution-role' AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Condition: StringEquals: 'aws:SourceAccount': !Ref 'AWS::AccountId' ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Tags: - Key: Purpose Value: M2M-Demo - Key: Project Value: !Ref ProjectName # Backend Lambda Function with updated runtime BackendLambda: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${ProjectName}-backend' Runtime: python3.12 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 30 MemorySize: 128 Description: 'Backend API for Cognito M2M demo' Tags: - Key: Purpose Value: M2M-Demo - Key: Project Value: !Ref ProjectName Code: ZipFile: | import json import logging # Configure logging logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context): try: logger.info(f"Received event: {json.dumps(event)}") http_method = event.get('httpMethod', '') path = event.get('path', '') # Handle GET /customers if http_method == 'GET' and path == '/customers': return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps({ 'customers': [ {'id': '123', 'name': 'John Doe', 'email': 'john@example.com'}, {'id': '456', 'name': 'Jane Smith', 'email': 'jane@example.com'} ], 'message': 'Successfully retrieved customers' }) } # Handle DELETE /customers/{id} elif http_method == 'DELETE' and '/customers/' in path: customer_id = path.split('/')[-1] logger.info(f"Deleting customer: {customer_id}") return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps({ 'message': f'Customer {customer_id} deleted successfully', 'customerId': customer_id }) } # Handle unsupported methods/paths else: return { 'statusCode': 404, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps({ 'error': 'Not found', 'message': f'Method {http_method} not supported for path {path}' }) } except Exception as e: logger.error(f"Error processing request: {str(e)}") return { 'statusCode': 500, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps({ 'error': 'Internal server error', 'message': 'An error occurred processing your request' }) } # API Gateway with enhanced configuration ApiGateway: Type: AWS::ApiGateway::RestApi Properties: Name: !Sub '${ProjectName}-api' Description: 'API with Cognito M2M Authorization' EndpointConfiguration: Types: - REGIONAL Tags: - Key: Purpose Value: M2M-Demo - Key: Project Value: !Ref ProjectName # Cognito Authorizer with enhanced configuration CognitoAuthorizer: Type: AWS::ApiGateway::Authorizer Properties: Name: !Sub '${ProjectName}-cognito-authorizer' RestApiId: !Ref ApiGateway Type: COGNITO_USER_POOLS ProviderARNs: - !GetAtt UserPool.Arn IdentitySource: method.request.header.Authorization AuthorizerResultTtlInSeconds: 300 # Customers Resource CustomersResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref ApiGateway ParentId: !GetAtt ApiGateway.RootResourceId PathPart: 'customers' # Customer ID Resource CustomerIdResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref ApiGateway ParentId: !Ref CustomersResource PathPart: '{id}' # GET /customers Method (Read scope required) GetCustomersMethod: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref ApiGateway ResourceId: !Ref CustomersResource HttpMethod: GET AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref CognitoAuthorizer AuthorizationScopes: - 'customer-api/read' MethodResponses: - StatusCode: '200' ResponseParameters: method.response.header.Access-Control-Allow-Origin: false - StatusCode: '401' ResponseParameters: method.response.header.Access-Control-Allow-Origin: false - StatusCode: '403' ResponseParameters: method.response.header.Access-Control-Allow-Origin: false Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${BackendLambda.Arn}/invocations' # OPTIONS /customers Method (CORS preflight) OptionsCustomersMethod: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref ApiGateway ResourceId: !Ref CustomersResource HttpMethod: OPTIONS AuthorizationType: NONE MethodResponses: - StatusCode: '200' ResponseParameters: method.response.header.Access-Control-Allow-Origin: false method.response.header.Access-Control-Allow-Headers: false method.response.header.Access-Control-Allow-Methods: false Integration: Type: MOCK RequestTemplates: application/json: '{"statusCode": 200}' IntegrationResponses: - StatusCode: '200' ResponseParameters: method.response.header.Access-Control-Allow-Origin: "'*'" method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" # DELETE /customers/{id} Method (Admin scope required) DeleteCustomerMethod: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref ApiGateway ResourceId: !Ref CustomerIdResource HttpMethod: DELETE AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref CognitoAuthorizer AuthorizationScopes: - 'customer-api/admin' MethodResponses: - StatusCode: '200' ResponseParameters: method.response.header.Access-Control-Allow-Origin: false - StatusCode: '401' ResponseParameters: method.response.header.Access-Control-Allow-Origin: false - StatusCode: '403' ResponseParameters: method.response.header.Access-Control-Allow-Origin: false Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${BackendLambda.Arn}/invocations' # OPTIONS /customers/{id} Method (CORS preflight) OptionsCustomerIdMethod: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref ApiGateway ResourceId: !Ref CustomerIdResource HttpMethod: OPTIONS AuthorizationType: NONE MethodResponses: - StatusCode: '200' ResponseParameters: method.response.header.Access-Control-Allow-Origin: false method.response.header.Access-Control-Allow-Headers: false method.response.header.Access-Control-Allow-Methods: false Integration: Type: MOCK RequestTemplates: application/json: '{"statusCode": 200}' IntegrationResponses: - StatusCode: '200' ResponseParameters: method.response.header.Access-Control-Allow-Origin: "'*'" method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'DELETE,OPTIONS'" Outputs: UserPoolId: Description: 'Cognito User Pool ID' Value: !Ref UserPool Export: Name: !Sub '${AWS::StackName}-UserPoolId' UserPoolArn: Description: 'Cognito User Pool ARN' Value: !GetAtt UserPool.Arn Export: Name: !Sub '${AWS::StackName}-UserPoolArn' TokenEndpoint: Description: 'OAuth2 Token Endpoint for M2M authentication' Value: !Sub 'https://${ProjectName}-${AWS::AccountId}.auth.${AWS::Region}.amazoncognito.com/oauth2/token' Export: Name: !Sub '${AWS::StackName}-TokenEndpoint' OrderClientId: Description: 'Order Processing Service Client ID (Full permissions: read, write, admin)' Value: !Ref OrderClient Export: Name: !Sub '${AWS::StackName}-OrderClientId' ReportingClientId: Description: 'Reporting Service Client ID (Read-only permissions)' Value: !Ref ReportingClient Export: Name: !Sub '${AWS::StackName}-ReportingClientId' ApiUrl: Description: 'API Gateway URL for testing endpoints' Value: !Sub 'https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod' Export: Name: !Sub '${AWS::StackName}-ApiUrl' JWKSEndpoint: Description: 'JWKS Endpoint for Token Validation' Value: !Sub 'https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}/.well-known/jwks.json' Export: Name: !Sub '${AWS::StackName}-JWKSEndpoint' TestEndpoints: Description: 'Test endpoints for validation' Value: !Sub | GET (read scope): https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod/customers DELETE (admin scope): https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod/customers/{id} CurlTestCommands: Description: 'Sample curl commands for testing (replace CLIENT_ID and CLIENT_SECRET)' Value: !Sub | # Get access token for Order Client (full permissions): curl -X POST https://${ProjectName}-${AWS::AccountId}.auth.${AWS::Region}.amazoncognito.com/oauth2/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=ORDER_CLIENT_ID&client_secret=ORDER_CLIENT_SECRET&scope=customer-api/read customer-api/write customer-api/admin" # Get access token for Reporting Client (read-only): curl -X POST https://${ProjectName}-${AWS::AccountId}.auth.${AWS::Region}.amazonaws.com/oauth2/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=REPORTING_CLIENT_ID&client_secret=REPORTING_CLIENT_SECRET&scope=customer-api/read" # Test GET customers (requires read scope): curl -X GET https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod/customers \ -H "Authorization: Bearer ACCESS_TOKEN" # Test DELETE customer (requires admin scope): curl -X DELETE https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod/customers/123 \ -H "Authorization: Bearer ACCESS_TOKEN"
⚠️ Remember to clean up test resources after validation to avoid ongoing costs!
- Language
- English
Relevant content
- Accepted Answerasked 2 years ago
