AWS Amplify Hosting for ML/AI Services Guide / ML Services Integration Guide
Part 2 of 2
AWS Amplify Hosting for ML/AI Services Guide
Part 2 of 2

AWS Amplify ML Services Integration Guide

Designing, Developing, Integrating and Deploying
Amplify Hosting for SPAs with Corporate ML Services Access

Enterprise-Grade Integration

This comprehensive guide provides detailed instructions for implementing secure, scalable AWS Amplify applications with integrated machine learning services including SageMaker and Bedrock, featuring enterprise authentication through Azure Entra ID and Lambda processors through private API Gateway integration.

Security First

Multi-layer security with JWT validation, WAF protection, and compliance frameworks

Production Ready

Complete implementation with CI/CD pipelines and Infrastructure as Code

ML Integration

Seamless integration with SageMaker and Bedrock for AI-powered applications

Version 1.1 | Updated: March 2026

Target Audience: Enterprise Developers, DevOps Engineers, Solution Architects

Table of Contents

1. Introduction and Overview
1.1 Purpose and Scope
1.2 Target Audience
1.3 Prerequisites
1.4 Architecture Benefits
2. Architecture Overview
2.1 High-Level Architecture
2.2 Component Descriptions
2.3 Architecture Layers
3. Authentication and Authorization
3.1 Authentication Flow
3.2 JWT Token Validation
3.3 Authorization Patterns
4. Data Flow Architecture
4.1 End-to-End Data Flow
4.2 SageMaker Integration
4.3 Bedrock Integration
5. Security Architecture
5.1 Multi-Layer Security Model
5.2 JWT Token Security
5.3 Data Protection and Compliance
6. Implementation Guide
6.1 Environment Setup
6.2 Cognito Configuration
6.3 API Gateway Setup
6.4 Lambda Implementation
6.5 Amplify Configuration
7. Deployment Guide
7.1 Infrastructure as Code
7.2 CI/CD Pipeline Setup
7.3 Environment Promotion
8. Best Practices and Considerations
8.1 Security Best Practices
8.2 Performance Optimization
8.3 Monitoring and Alerting
9. Troubleshooting and Monitoring
9.1 Common Issues and Solutions
9.2 Monitoring Setup
10. Appendix
10.1 Reference Links
10.2 Glossary
10.3 Version History

1. Introduction and Overview

1.1 Purpose and Scope

This guide provides comprehensive instructions for designing, developing, integrating, and deploying AWS Amplify hosting solutions for Single Page Applications (SPAs), specifically Angular applications, with secure access to corporate machine learning services including Amazon SageMaker and Amazon Bedrock.

The integration architecture emphasizes enterprise-grade security through Azure Entra ID authentication via Amazon Cognito, private API Gateway configurations with AWS WAF protection, and Lambda-based processors for ML service orchestration.

1.2 Target Audience

  • Enterprise Developers: Frontend and backend developers implementing Angular SPAs with AWS ML services
  • DevOps Engineers: Infrastructure specialists managing CI/CD pipelines and deployment automation
  • Solution Architects: Technical architects designing secure, scalable ML-enabled applications
  • Security Engineers: Professionals implementing enterprise authentication and authorization patterns

1.3 Prerequisites

Technical Prerequisites

  • AWS Account with appropriate permissions for Amplify, Cognito, API Gateway, Lambda, SageMaker, and Bedrock
  • Azure Entra ID (formerly Azure AD) tenant with administrative access
  • Node.js 18+ and npm/yarn package manager
  • Angular CLI 16+ and TypeScript knowledge
  • AWS CLI configured with appropriate credentials
  • Git version control system

Knowledge Prerequisites

  • Intermediate Angular development experience
  • Understanding of OAuth 2.0 and OpenID Connect protocols
  • Basic knowledge of AWS services and IAM concepts
  • Familiarity with RESTful API design and implementation
  • Understanding of JWT tokens and authentication flows

1.4 Architecture Benefits

Security Benefits

  • • Multi-layer authentication and authorization
  • • JWT token validation with Lambda authorizers
  • • WAF protection against common attacks
  • • Private API Gateway with VPC integration
  • • Compliance with SOC 2, GDPR, HIPAA standards

Performance Benefits

  • • CloudFront CDN for global content delivery
  • • Lambda@Edge for edge computing capabilities
  • • Optimized ML model inference through SageMaker
  • • Efficient token caching and validation
  • • Auto-scaling Lambda processors

Scalability Benefits

  • • Serverless architecture with automatic scaling
  • • Elastic ML model endpoints
  • • Distributed authentication through Cognito
  • • API Gateway throttling and rate limiting
  • • Multi-region deployment capabilities

Operational Benefits

  • • Infrastructure as Code with CDK/CloudFormation
  • • Automated CI/CD pipelines
  • • Comprehensive monitoring and alerting
  • • Cost optimization through serverless pricing
  • • Simplified maintenance and updates

2. Architecture Overview

2.1 High-Level Architecture

Overall System Architecture

User BrowserAngular SPA
App Delivery
WAFEdge Protection
CloudFront CDNGlobal Distribution
Amplify HostingS3 + Build Pipeline
Authentication
Amazon CognitoUser Pool + Federation
Entra IDCorporate Identity (SAML)
SPA sends JWT in API request
API Layer
WAFAPI Protection
API GatewayRegional + WAF
Lambda AuthorizerJWT Validation
Lambda ProcessorsML Orchestration
ML Services Layer
SageMakerCustom Model Inference
BedrockFoundation Models

Figure 1: Overall System Architecture

Amplify CloudFront Integration Architecture

User Browser
Angular SPA
HTTPS Requests
CloudFront
CDN + Edge Locations
Caching & Distribution
Amplify Hosting
S3 + Build Pipeline
Static Asset Serving

Private API Gateway with WAF Protection

Layer 1: WAF Protection
SQL Injection, XSS, Rate Limiting, IP Filtering
Layer 2: API Gateway
Private Endpoints, Request Validation, Throttling
Layer 3: Lambda Authorizer
JWT Validation, User Authorization, Token Caching
Layer 4: Lambda Processors
Business Logic, ML Service Integration, Response Processing

2.2 Component Descriptions

Component Purpose Key Features Integration Points
Angular SPA Frontend application Responsive UI, Authentication, ML Service Calls Cognito, API Gateway
AWS Amplify Hosting & CI/CD Auto-deployment, SSL, Custom Domains CloudFront, S3, CodeCommit
CloudFront Content Delivery Global CDN, Edge Caching, SSL Termination Amplify, Lambda@Edge
Cognito Authentication User Pools, Identity Pools, SAML/OIDC Azure Entra ID, API Gateway
API Gateway API Management Private Endpoints, Throttling, Monitoring Lambda, WAF, VPC
Lambda Authorizer Authorization JWT Validation, Policy Generation, Caching Cognito, API Gateway
Lambda Processors Business Logic ML Orchestration, Data Processing, Response Formatting SageMaker, Bedrock, DynamoDB
SageMaker ML Inference Custom Models, Real-time Endpoints, Batch Processing Lambda, S3, ECR
Bedrock Foundation Models LLMs, Text Generation, Embeddings Lambda, S3, CloudWatch

2.3 Architecture Layers

Frontend Layer

Angular SPA hosted on AWS Amplify with CloudFront distribution

  • • Responsive design with Angular Material
  • • Authentication integration with Cognito
  • • HTTP interceptors for token management
  • • Error handling and user feedback

Authentication Layer

Cognito User Pools with Azure Entra ID federation

  • • SAML 2.0 integration with Azure Entra ID
  • • JWT token generation and validation
  • • User groups and role-based access control
  • • Multi-factor authentication support

API Layer

Private API Gateway with WAF protection and Lambda authorization

  • • Private endpoints with VPC integration
  • • Request/response validation and transformation
  • • Rate limiting and throttling policies
  • • Comprehensive logging and monitoring

ML Services Layer

SageMaker and Bedrock integration through Lambda processors

  • • Real-time model inference endpoints
  • • Foundation model access through Bedrock
  • • Asynchronous processing capabilities
  • • Model versioning and A/B testing support

3. Authentication and Authorization

3.1 Authentication Flow

Complete Authentication and Authorization Flow

1. User Login Request Angular SPA → Cognito
2. SAML Authentication Cognito → Azure Entra ID
3. JWT Token Generation Cognito → Angular SPA
4. API Request with Token Angular SPA → API Gateway
5. Token Validation API Gateway → Lambda Authorizer
6. ML Service Processing Lambda Processor → SageMaker/Bedrock

3.2 JWT Token Validation Process

JWT Token Validation Process

Token Structure Validation
Header.Payload.Signature verification
Signature Verification
RSA256 with Cognito public keys
Claims Validation
iss, aud, exp, iat verification
User Authorization
Group membership and permissions
Policy Generation
IAM policy for API Gateway
Context Enrichment
User metadata for downstream services

Security Note: Lambda Authorizer Caching

Lambda Authorizer responses are cached by API Gateway for up to 1 hour by default. Ensure your caching strategy accounts for user permission changes and token expiration. Consider implementing cache invalidation for critical permission updates.

3.3 Authorization Patterns

Authorization Pattern Use Case Implementation Security Level
Role-Based Access Control (RBAC) Standard user permissions Cognito Groups + Lambda Authorizer Medium
Attribute-Based Access Control (ABAC) Dynamic permissions JWT Claims + Context Evaluation High
Resource-Based Authorization Data-specific access Lambda Function + DynamoDB High
Time-Based Access Scheduled operations JWT exp claim + Lambda validation Medium

4. Data Flow Architecture

4.1 End-to-End Data Flow

Complete Data Flow Architecture

User Input
Angular Form
Authentication
JWT Token
API Gateway
Request Validation
Authorization
Lambda Authorizer
Lambda Processor
Business Logic
ML Service
SageMaker/Bedrock
Response Processing
Data Formatting
Angular SPA
UI Update & User Feedback

4.2 SageMaker Integration Flow

SageMaker Integration Flow

1. Data Preprocessing Lambda → Data Validation & Transformation
2. Model Endpoint Invocation Lambda → SageMaker Real-time Endpoint
3. Inference Processing SageMaker → Model Prediction
4. Result Postprocessing Lambda → Response Formatting

Lambda Function Configuration for SageMaker

Configure your Lambda function with appropriate timeout and memory allocation based on your model's requirements. Critical: For synchronous API Gateway integrations, API Gateway enforces a hard 29-second integration timeout, regardless of the Lambda timeout setting. Long-running inference jobs must use an asynchronous pattern (SQS + polling, WebSocket, or Step Functions).

  • • Memory: 128MB – 10,240MB (10 GB)
  • • Timeout (sync via API Gateway): Maximum effective timeout is 28 seconds — API Gateway hard limit is 29s
  • • Timeout (async invocation via SQS/EventBridge): Up to 15 minutes (900 seconds)
  • • For inference >28s: Use async pattern — return a Job ID immediately and poll for results
  • • Concurrent executions: Configure based on SageMaker endpoint capacity
  • • Error handling: Implement retry logic with exponential backoff

4.3 Bedrock Integration Flow

Bedrock Integration Flow

1. Prompt Engineering Lambda → Prompt Template Processing
2. Foundation Model Invocation Lambda → Bedrock Runtime API
3. Model Processing Bedrock → Foundation Model Inference
4. Response Parsing Lambda → Content Extraction & Formatting

Bedrock Use Cases

Text Generation
  • • Content creation and summarization
  • • Code generation and documentation
  • • Email and report writing
Conversational AI
  • • Chatbots and virtual assistants
  • • Customer support automation
  • • Interactive Q&A systems
Text Analysis
  • • Sentiment analysis and classification
  • • Entity extraction and recognition
  • • Language translation
Embeddings
  • • Semantic search and similarity
  • • Recommendation systems
  • • Document clustering

5. Security Architecture

5.1 Multi-Layer Security Model

Defense in Depth Security Architecture

Layer 1: Edge Security - CloudFront + WAF + DDoS Protection
Layer 2: Network Security - VPC + Private Subnets + Security Groups
Layer 3: API Security - Private API Gateway + Request Validation
Layer 4: Authentication - Cognito + Azure Entra ID + MFA
Layer 5: Authorization - Lambda Authorizer + JWT Validation + RBAC
Layer 6: Application Security - Lambda Functions + IAM Roles + Encryption
Layer 7: Data Security - Encryption at Rest + In Transit + Key Management

5.2 JWT Token Security Implementation

Token Structure

Header: Algorithm (RS256), Token Type (JWT)
Payload: User Claims, Groups, Permissions, Expiration
Signature: RSA256 with Cognito Private Key

Security Features

• Short expiration times (1-24 hours)
• Refresh token rotation
• Audience validation
• Issuer verification
• Signature validation with public keys

5.3 Data Protection and Compliance

Encryption Strategy

Data in Transit

  • • TLS 1.2 minimum for all HTTPS connections (TLS 1.3 negotiated where supported)
  • • CloudFront Security Policy: TLSv1.2_2021 recommended
  • • Certificate pinning in mobile apps
  • • API Gateway SSL termination
  • • VPC endpoint encryption

Data at Rest

  • • S3 bucket encryption with KMS
  • • DynamoDB encryption at rest
  • • Lambda environment variable encryption
  • • CloudWatch Logs encryption

Compliance Framework Support

SOC 2 Type II

  • • Security controls documentation
  • • Availability monitoring
  • • Processing integrity validation
  • • Confidentiality measures

GDPR Compliance

  • • Data minimization principles
  • • Right to erasure implementation
  • • Consent management
  • • Data portability features

HIPAA (Healthcare)

  • • PHI encryption and access controls
  • • Audit logging and monitoring
  • • Business Associate Agreements
  • • Risk assessment procedures

PCI DSS (Payments)

  • • Secure network architecture
  • • Cardholder data protection
  • • Vulnerability management
  • • Regular security testing

Audit and Monitoring

CloudTrail Logging

  • • API call logging
  • • User activity tracking
  • • Resource access monitoring

CloudWatch Monitoring

  • • Real-time metrics
  • • Custom dashboards
  • • Automated alerting

Security Hub

  • • Centralized findings
  • • Compliance status
  • • Security standards

6Implementation Guide

6.1 Environment Setup Prerequisites

Before writing any application code, set up the full toolchain. Complete all four steps below in order.

  1. Install Node.js 18 LTSRequired runtime for Angular CLI, AWS CDK, and all JavaScript tooling.
  2. Install global CLIsAngular CLI, AWS CLI v2, and AWS CDK must all be available on your $PATH.
  3. Configure AWS credentialsRun aws configure with an IAM user or role that has permissions for Cognito, API Gateway, Lambda, Amplify, and Bedrock.
  4. Install project dependenciesRun npm ci inside both frontend/ and infrastructure/.
sh
scripts/bootstrap.sh
# 1 — Node.js 18 LTS (Debian/Ubuntu)
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs

# 2 — Global CLIs
npm install -g @angular/cli@16
npm install -g aws-cdk

# AWS CLI v2
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscliv2.zip
unzip awscliv2.zip && sudo ./aws/install

# 3 — Configure AWS credentials (interactive)
aws configure

# 4 — Install project dependencies
(cd frontend       && npm ci)
(cd infrastructure && npm ci)

Project Directory Structure

amplify-ml-app/ ├── frontend/ # Angular SPA │ └── src/ │ ├── app/ │ │ ├── auth/ # Auth module + service + interceptor │ │ ├── ml-services/ # ML API service + components │ │ └── shared/ # Shared utilities, error interceptor │ └── environments/ # Dev + prod environment configs ├── infrastructure/ # AWS CDK stacks │ └── lib/ │ ├── cognito-stack.ts │ ├── api-gateway-stack.ts │ └── main-stack.ts ├── lambda/ │ ├── authorizer/ # JWT validation (Python) │ └── ml-processor/ # Bedrock / SageMaker calls (Python) └── amplify.yml # Amplify Hosting build spec (frontend only)

Environment Files — Dev vs Production

The key difference between environments is the domainPrefix. This is the Cognito hosted-UI prefix set under User Pool → App Integration → Domainnot the userPoolId.

Development
// src/environments/environment.ts
export const environment = {
  production: false,
  cognito: {
    userPoolId: 'us-east-1_XXXXXXXXX',
    userPoolWebClientId: 'XXXXXXXXXXXXXXXXXX',
    // Cognito hosted-UI prefix — NOT userPoolId
    domainPrefix: 'my-company-ml-app-dev',
    region: 'us-east-1',
    identityPoolId:
      'us-east-1:XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX'
  },
  api: {
    baseUrl: 'https://api.example.com/dev',
    region: 'us-east-1'
  }
};
Production
// src/environments/environment.prod.ts
export const environment = {
  production: true,
  cognito: {
    userPoolId: 'us-east-1_YYYYYYYYY',
    userPoolWebClientId: 'YYYYYYYYYYYYYYYYYY',
    // Cognito hosted-UI prefix — NOT userPoolId
    domainPrefix: 'my-company-ml-app',
    region: 'us-east-1',
    identityPoolId:
      'us-east-1:YYYYYYYY-YYYY-YYYY-YYYYYYYYYYYY'
  },
  api: {
    baseUrl: 'https://api.example.com/prod',
    region: 'us-east-1'
  }
};

6.2 Cognito Configuration CDK

The CognitoStack creates the User Pool, App Client, user groups, and the Entra ID SAML identity provider. Deploy this stack first — all other stacks reference its outputs.

App Client settings generateSecret: false is required for browser-based SPAs — secret-based flows cannot run in a browser. Token validity is set to 1 hour (access/ID) with a 30-day refresh window.
TS
infrastructure/lib/cognito-stack.ts
import * as cdk     from 'aws-cdk-lib';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import { Construct } from 'constructs';

export class CognitoStack extends cdk.Stack {
  public readonly userPool:       cognito.UserPool;
  public readonly userPoolClient: cognito.UserPoolClient;

  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ── User Pool ─────────────────────────────────────────────────
    this.userPool = new cognito.UserPool(this, 'MLAppUserPool', {
      userPoolName:      'ml-app-user-pool',
      selfSignUpEnabled: false,          // Corporate users only
      signInAliases:     { email: true },
      passwordPolicy: {
        minLength:        12,
        requireLowercase: true,
        requireUppercase: true,
        requireDigits:    true,
        requireSymbols:   true,
      },
      mfa:             cognito.Mfa.REQUIRED,
      mfaSecondFactor: { sms: true, otp: true },
      accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
      removalPolicy:   cdk.RemovalPolicy.DESTROY,
    });

    // ── App Client ────────────────────────────────────────────────
    this.userPoolClient = new cognito.UserPoolClient(this, 'MLAppClient', {
      userPool:           this.userPool,
      userPoolClientName: 'ml-app-client',
      generateSecret:     false,   // Required for browser SPAs
      authFlows:          { userSrp: true },
      oAuth: {
        flows:    { authorizationCodeGrant: true },
        scopes:   [
          cognito.OAuthScope.OPENID,
          cognito.OAuthScope.EMAIL,
          cognito.OAuthScope.PROFILE,
        ],
        callbackUrls: [
          'https://localhost:4200/auth/callback',
          'https://your-domain.amplifyapp.com/auth/callback',
        ],
        logoutUrls: [
          'https://localhost:4200/auth/logout',
          'https://your-domain.amplifyapp.com/auth/logout',
        ],
      },
      accessTokenValidity:  cdk.Duration.hours(1),
      idTokenValidity:      cdk.Duration.hours(1),
      refreshTokenValidity: cdk.Duration.days(30),
    });

    // ── User Groups ───────────────────────────────────────────────
    [
      { name: 'Administrators', desc: 'Full ML access',     precedence: 1 },
      { name: 'MLUsers',        desc: 'Standard ML access', precedence: 2 },
      { name: 'ReadOnly',       desc: 'Read-only access',   precedence: 3 },
    ].forEach(g =>
      new cognito.CfnUserPoolGroup(this, `Group-${g.name}`, {
        userPoolId:  this.userPool.userPoolId,
        groupName:   g.name,
        description: g.desc,
        precedence:  g.precedence,
      })
    );

    // ── Entra ID SAML Identity Provider ──────────────────────────
    const samlProvider = new cognito.CfnUserPoolIdentityProvider(
      this, 'EntraIDProvider', {
        userPoolId:   this.userPool.userPoolId,
        providerName: 'EntraID',
        providerType: 'SAML',
        providerDetails: {
          MetadataURL:
            'https://login.microsoftonline.com/{tenant-id}'
            + '/federationmetadata/2007-06/federationmetadata.xml',
          IDPSignout: 'true',
        },
        attributeMapping: {
          email:       'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
          given_name:  'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname',
          family_name: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname',
        },
      }
    );
    this.userPoolClient.node.addDependency(samlProvider);
  }
}

6.3 API Gateway Setup CDK

Two critical production settings to get right
  • CORS origins — never use Cors.ALL_ORIGINS (wildcard *) in production. Enumerate only your actual Amplify and custom domains.
  • dataTraceEnabled: false — must be false in production. When true, full request/response payloads including sensitive ML inputs are written to CloudWatch Logs — a serious security and compliance risk.
TS
infrastructure/lib/api-gateway-stack.ts
import * as cdk        from 'aws-cdk-lib';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda     from 'aws-cdk-lib/aws-lambda';
import * as wafv2      from 'aws-cdk-lib/aws-wafv2';
import * as logs       from 'aws-cdk-lib/aws-logs';
import { Construct }   from 'constructs';

export class ApiGatewayStack extends cdk.Stack {
  public readonly api: apigateway.RestApi;

  constructor(scope: Construct, id: string, props: {
    authorizerFunction:  lambda.Function;
    mlProcessorFunction: lambda.Function;
  } & cdk.StackProps) {
    super(scope, id, props);

    // ── WAF Web ACL ───────────────────────────────────────────────
    const webAcl = new wafv2.CfnWebACL(this, 'MLAppWebACL', {
      scope:         'REGIONAL',
      defaultAction: { allow: {} },
      rules: [
        {
          name: 'AWSManagedRulesCommonRuleSet', priority: 1,
          overrideAction: { none: {} },
          statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesCommonRuleSet' } },
          visibilityConfig: { sampledRequestsEnabled: true, cloudWatchMetricsEnabled: true, metricName: 'CommonRuleSet' },
        },
        {
          name: 'AWSManagedRulesKnownBadInputsRuleSet', priority: 2,
          overrideAction: { none: {} },
          statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesKnownBadInputsRuleSet' } },
          visibilityConfig: { sampledRequestsEnabled: true, cloudWatchMetricsEnabled: true, metricName: 'KnownBadInputs' },
        },
        {
          name: 'RateLimitRule', priority: 3,
          action: { block: {} },
          statement: { rateBasedStatement: { limit: 2000, aggregateKeyType: 'IP' } },
          visibilityConfig: { sampledRequestsEnabled: true, cloudWatchMetricsEnabled: true, metricName: 'RateLimit' },
        },
      ],
      visibilityConfig: { sampledRequestsEnabled: true, cloudWatchMetricsEnabled: true, metricName: 'MLAppWebACL' },
    });

    // ── REST API ──────────────────────────────────────────────────
    this.api = new apigateway.RestApi(this, 'MLAppAPI', {
      restApiName:           'ML App API',
      endpointConfiguration: { types: [apigateway.EndpointType.REGIONAL] },

      // ⚠ List explicit origins — never use Cors.ALL_ORIGINS in production
      defaultCorsPreflightOptions: {
        allowOrigins:     ['https://your-domain.amplifyapp.com'],
        allowMethods:     ['GET', 'POST', 'OPTIONS'],
        allowHeaders:     ['Content-Type', 'Authorization', 'X-Amz-Date', 'X-Api-Key'],
        allowCredentials: true,
      },

      deployOptions: {
        stageName:            'prod',
        throttlingRateLimit:   1000,
        throttlingBurstLimit:  2000,
        loggingLevel:          apigateway.MethodLoggingLevel.ERROR,
        dataTraceEnabled:      false,   // ⚠ NEVER true in production
        metricsEnabled:        true,
        // Structured access logging — use instead of dataTrace
        accessLogDestination: new apigateway.LogGroupLogDestination(
          new logs.LogGroup(this, 'ApiAccessLogs', {
            retention: logs.RetentionDays.THIRTY_DAYS,
          })
        ),
        accessLogFormat: apigateway.AccessLogFormat.jsonWithStandardFields(),
      },
    });

    // Associate WAF
    new wafv2.CfnWebACLAssociation(this, 'WebACLAssoc', {
      resourceArn: this.api.deploymentStage.stageArn,
      webAclArn:   webAcl.attrArn,
    });

    // ── Lambda Authorizer ─────────────────────────────────────────
    const authorizer = new apigateway.TokenAuthorizer(this, 'JWTAuthorizer', {
      handler:         props.authorizerFunction,
      identitySource:  'method.request.header.Authorization',
      authorizerName:  'JWTAuthorizer',
      resultsCacheTtl: cdk.Duration.minutes(5),
    });

    // ── API Routes ────────────────────────────────────────────────
    const ml = this.api.root.addResource('ml');

    ml.addResource('sagemaker').addMethod(
      'POST',
      new apigateway.LambdaIntegration(props.mlProcessorFunction),
      { authorizer }
    );

    ml.addResource('bedrock').addMethod(
      'POST',
      new apigateway.LambdaIntegration(props.mlProcessorFunction),
      { authorizer }
    );
  }
}

6.4 Lambda Authorizer Python

The authorizer validates every JWT access token before any request reaches your ML services. It runs five checks in sequence:

  1. Extract tokenStrip the Bearer prefix from the Authorization header.
  2. Decode header (no verify)Read the unverified JWT header to get the kid (key ID) for JWKS key lookup.
  3. Verify RS256 signatureFetch the matching RSA public key from Cognito's JWKS endpoint and verify cryptographically.
  4. Validate claimsCheck exp, iss, client_id (not aud), and token_use == "access".
  5. Check group membershipRead cognito:groups (not groups) and allow only Administrators and MLUsers.
🚫
Two Cognito-specific quirks — easy to get wrong
  • Access tokens carry client_id, not aud. Passing audience= to jwt.decode() always raises InvalidAudienceError. Use options={"verify_aud": False} and check client_id manually.
  • Group membership is in cognito:groups, not groups. Using the wrong key silently returns an empty list and denies every user.
py
lambda/authorizer/index.py
import json, os, logging
from functools import lru_cache
from typing import Dict, Any, Optional

import jwt
import requests

logger = logging.getLogger()
logger.setLevel(logging.INFO)

COGNITO_REGION = os.environ['COGNITO_REGION']
USER_POOL_ID   = os.environ['USER_POOL_ID']
CLIENT_ID      = os.environ['CLIENT_ID']   # Validated manually — NOT via audience=
JWKS_URL = (
    f'https://cognito-idp.{COGNITO_REGION}.amazonaws.com'
    f'/{USER_POOL_ID}/.well-known/jwks.json'
)


@lru_cache(maxsize=1)
def get_jwks() -> Dict:
    """Fetch JWKS once and cache for the Lambda container lifetime."""
    resp = requests.get(JWKS_URL, timeout=10)
    resp.raise_for_status()
    return resp.json()


def get_public_key(kid: str):
    """Return RSA public key matching the token's kid."""
    for key in get_jwks()['keys']:
        if key['kid'] == kid:
            return jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key))
    raise ValueError(f'Public key not found for kid: {kid}')


def validate_token(token: str) -> Dict:
    """Verify signature and validate claims. Returns claims dict."""
    header     = jwt.get_unverified_header(token)
    public_key = get_public_key(header['kid'])

    # ⚠ Cognito ACCESS tokens use client_id, NOT aud — disable audience check
    claims = jwt.decode(
        token,
        public_key,
        algorithms=['RS256'],
        options={'verify_aud': False},
        issuer=(
            f'https://cognito-idp.{COGNITO_REGION}.amazonaws.com'
            f'/{USER_POOL_ID}'
        ),
    )

    # Validate client_id manually (replaces the standard audience check)
    if claims.get('client_id') != CLIENT_ID:
        raise ValueError('client_id mismatch')

    # Confirm access token (not ID token)
    if claims.get('token_use') != 'access':
        raise ValueError("token_use must be 'access'")

    return claims


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


def lambda_handler(event: Dict, context: Any) -> Dict:
    try:
        token = event.get('authorizationToken', '').replace('Bearer ', '')
        if not token:
            raise ValueError('No token provided')

        claims   = validate_token(token)
        username = claims.get('username', claims.get('sub'))

        # ⚠ Group claim is cognito:groups — NOT groups
        groups = claims.get('cognito:groups', [])

        if not any(g in {'Administrators', 'MLUsers'} for g in groups):
            logger.warning(f'User {username} lacks required group')
            return generate_policy(username, 'Deny', event['methodArn'])

        logger.info(f'Authorised: {username}  groups={groups}')
        return generate_policy(username, 'Allow', event['methodArn'], {
            'username': username,
            'email':    claims.get('email', ''),
            'groups':   ','.join(groups),
            'sub':      claims.get('sub', ''),
        })

    except Exception as exc:
        logger.error(f'Authorizer error: {exc}')
        raise Exception('Unauthorized')
txt
lambda/authorizer/requirements.txt
PyJWT==2.8.0
cryptography==41.0.3
requests==2.31.0

6.5 Amplify App Configuration Angular + Amplify v6

Amplify v6 breaking change — Auth API is now modular The legacy import { Auth } from 'aws-amplify' class no longer exists in v6. All auth functions must be imported individually from 'aws-amplify/auth'. The Amplify.configure() structure changed too — the OAuth domain field takes your domainPrefix, not the userPoolId.

app.module.ts — Amplify v6 bootstrap

TS
frontend/src/app/app.module.ts
import { NgModule }            from '@angular/core';
import { BrowserModule }       from '@angular/platform-browser';
import { HttpClientModule,
         HTTP_INTERCEPTORS }   from '@angular/common/http';
import { ReactiveFormsModule } from '@angular/forms';

// Amplify v6 — configure once at module load time
import { Amplify }     from 'aws-amplify';
import { environment } from '../environments/environment';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent }     from './app.component';
import { AuthModule }       from './auth/auth.module';
import { MLServicesModule } from './ml-services/ml-services.module';
import { AuthInterceptor }  from './auth/auth.interceptor';
import { ErrorInterceptor } from './shared/error.interceptor';

// Amplify v6 configuration — domainPrefix is NOT the same as userPoolId
Amplify.configure({
  Auth: {
    Cognito: {
      userPoolId:       environment.cognito.userPoolId,
      userPoolClientId: environment.cognito.userPoolWebClientId,
      identityPoolId:   environment.cognito.identityPoolId,
      loginWith: {
        oauth: {
          // Format: <domainPrefix>.auth.<region>.amazoncognito.com
          domain:          `${environment.cognito.domainPrefix}.auth.${environment.cognito.region}.amazoncognito.com`,
          scopes:          ['email', 'openid', 'profile'],
          redirectSignIn:  [window.location.origin + '/auth/callback'],
          redirectSignOut: [window.location.origin + '/auth/logout'],
          responseType:    'code',
        },
      },
    },
  },
});

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule, HttpClientModule, ReactiveFormsModule,
    AppRoutingModule, AuthModule, MLServicesModule,
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor,  multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
  ],
  bootstrap: [AppComponent],
})
export class AppModule { }

auth.service.ts — Amplify v6 named function imports

TS
frontend/src/app/auth/auth.service.ts
import { Injectable }      from '@angular/core';
import { Router }          from '@angular/router';
import { BehaviorSubject } from 'rxjs';

// Amplify v6 — named function imports, no Auth class
import {
  signIn, signOut, getCurrentUser,
  fetchAuthSession, signInWithRedirect,
  type SignInOutput,
} from 'aws-amplify/auth';

export interface User {
  username: string;
  email:    string;
  groups:   string[];
  userId:   string;
}

@Injectable({ providedIn: 'root' })
export class AuthService {
  private userSubject = new BehaviorSubject<User | null>(null);
  public  currentUser$ = this.userSubject.asObservable();

  constructor(private router: Router) { this.initializeAuth(); }

  private async initializeAuth(): Promise<void> {
    try {
      const { username, userId } = await getCurrentUser();
      const session  = await fetchAuthSession();
      const payload  = session.tokens?.idToken?.payload;
      this.userSubject.next({
        username, userId,
        email: payload?.['email'] as string || '',
        // ⚠ Group claim key is cognito:groups — NOT groups
        groups: payload?.['cognito:groups'] as string[] || [],
      });
    } catch { /* no active session */ }
  }

  /** Redirect to Entra ID via Cognito hosted-UI */
  async signInWithSAML(): Promise<void> {
    await signInWithRedirect({ provider: { custom: 'EntraID' } });
  }

  /** Returns the access token string for Authorization headers */
  async getAccessToken(): Promise<string> {
    const session = await fetchAuthSession();
    return session.tokens?.accessToken?.toString() ?? '';
  }

  async signOut(): Promise<void> {
    await signOut();
    this.userSubject.next(null);
    this.router.navigate(['/auth/login']);
  }

  isAuthenticated():           boolean { return this.userSubject.value !== null; }
  hasRole(r: string):          boolean { return this.userSubject.value?.groups.includes(r) ?? false; }
  hasAnyRole(rs: string[]):    boolean { return rs.some(r => this.hasRole(r)); }
}

ml-api.service.ts — Calling ML endpoints with auth

TS
frontend/src/app/ml-services/ml-api.service.ts
import { Injectable }                  from '@angular/core';
import { HttpClient, HttpHeaders }     from '@angular/common/http';
import { Observable, from, switchMap } from 'rxjs';
import { AuthService }                 from '../auth/auth.service';
import { environment }                 from '../../environments/environment';

export interface SageMakerRequest { modelName: string; inputData: any; parameters?: any; }
export interface BedrockRequest   { modelId: string;   prompt: string; parameters?: any; }
export interface MLResponse       { success: boolean;  data: any;      error?: string;   }

@Injectable({ providedIn: 'root' })
export class MLApiService {
  private readonly base = environment.api.baseUrl;

  constructor(private http: HttpClient, private auth: AuthService) {}

  /** Build an Authorization header from the current access token */
  private headers$(): Observable<HttpHeaders> {
    return from(this.auth.getAccessToken()).pipe(
      switchMap(token => [new HttpHeaders({
        'Content-Type':  'application/json',
        'Authorization': `Bearer ${token}`,
      })])
    );
  }

  invokeSageMaker(req: SageMakerRequest): Observable<MLResponse> {
    return this.headers$().pipe(
      switchMap(h => this.http.post<MLResponse>(`${this.base}/ml/sagemaker`, req, { headers: h }))
    );
  }

  invokeBedrock(req: BedrockRequest): Observable<MLResponse> {
    return this.headers$().pipe(
      switchMap(h => this.http.post<MLResponse>(`${this.base}/ml/bedrock`, req, { headers: h }))
    );
  }
}

7Deployment Guide

7.1 Infrastructure as Code CDK

The MainStack composes the Cognito, Lambda, and API Gateway stacks. It emits three outputs — User Pool ID, Client ID, and API URL — that the deployment script uses to write the frontend environment files automatically.

Deployment order CDK resolves cross-stack references automatically. Run cdk deploy --all and CDK will sequence CognitoStack first, then Lambdas, then ApiGatewayStack.
TS
infrastructure/lib/main-stack.ts
import * as cdk    from 'aws-cdk-lib';
import * as lambda  from 'aws-cdk-lib/aws-lambda';
import * as iam     from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
import { CognitoStack }    from './cognito-stack';
import { ApiGatewayStack } from './api-gateway-stack';

export class MainStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ── 1. Cognito — deploy first, outputs used below ─────────────
    const cognitoStack = new CognitoStack(this, 'CognitoStack');

    // ── 2. Lambda Authorizer (JWT validation) ─────────────────────
    const authorizerFn = new lambda.Function(this, 'AuthorizerFn', {
      runtime:     lambda.Runtime.PYTHON_3_12,
      handler:     'index.lambda_handler',
      code:        lambda.Code.fromAsset('lambda/authorizer'),
      timeout:     cdk.Duration.seconds(30),
      memorySize:  512,
      environment: {
        COGNITO_REGION: this.region,
        USER_POOL_ID:   cognitoStack.userPool.userPoolId,
        CLIENT_ID:      cognitoStack.userPoolClient.userPoolClientId,
      },
    });

    // ── 3. ML Processor Lambda (SageMaker + Bedrock calls) ────────
    const mlProcessorFn = new lambda.Function(this, 'MLProcessorFn', {
      runtime:     lambda.Runtime.PYTHON_3_12,
      handler:     'index.lambda_handler',
      code:        lambda.Code.fromAsset('lambda/ml-processor'),
      timeout:     cdk.Duration.minutes(15),  // Long-running inference
      memorySize:  2048,                       // Lambda supports 128 MB – 10,240 MB
      environment: { REGION: this.region },
    });

    // Least-privilege: grant only the ML actions needed
    mlProcessorFn.addToRolePolicy(new iam.PolicyStatement({
      effect:  iam.Effect.ALLOW,
      actions: [
        'sagemaker:InvokeEndpoint',
        'bedrock:InvokeModel',
        'bedrock:InvokeModelWithResponseStream',
      ],
      resources: ['*'],  // Narrow to specific ARNs in production
    }));

    // ── 4. API Gateway (depends on Lambda functions above) ────────
    const apiStack = new ApiGatewayStack(this, 'ApiGatewayStack', {
      authorizerFunction:  authorizerFn,
      mlProcessorFunction: mlProcessorFn,
    });

    // ── Stack outputs ─────────────────────────────────────────────
    new cdk.CfnOutput(this, 'UserPoolId',
      { value: cognitoStack.userPool.userPoolId,             description: 'Cognito User Pool ID' });
    new cdk.CfnOutput(this, 'UserPoolClientId',
      { value: cognitoStack.userPoolClient.userPoolClientId, description: 'Cognito App Client ID' });
    new cdk.CfnOutput(this, 'ApiGatewayUrl',
      { value: apiStack.api.url,                             description: 'API Gateway base URL' });
  }
}
TS
infrastructure/bin/infrastructure.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { MainStack } from '../lib/main-stack';

const app = new cdk.App();

new MainStack(app, 'MLAppStack', {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region:  process.env.CDK_DEFAULT_REGION || 'us-east-1',
  },
  tags: {
    Project:     'ML-App',
    Environment: process.env.ENVIRONMENT || 'dev',
  },
});

7.2 CI/CD Pipeline Setup GitHub Actions

Three jobs run in sequence: testdeploy-infrastructuredeploy-frontend. CDK infrastructure runs in a dedicated GitHub Actions job — never inside Amplify Hosting.

amplify.yml is for the frontend build only Never place cdk deploy inside the Amplify build spec. The Amplify build role has minimal IAM permissions by design. All CDK deployments must run from a GitHub Actions runner (or AWS CodePipeline) that assumes a role with the required permissions.

amplify.yml — Frontend-only build spec

yml
amplify.yml
version: 1
frontend:
  phases:
    preBuild:
      commands:
        - cd frontend
        - npm ci
    build:
      commands:
        - npm run build:prod
  artifacts:
    baseDirectory: frontend/dist/ml-app   # Match your Angular project dist folder name
    files:
      - '**/*'
  cache:
    paths:
      - frontend/node_modules/**/*

deploy.yml — GitHub Actions full pipeline

yml
.github/workflows/deploy.yml
name: Deploy ML App

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  AWS_REGION:   us-east-1
  NODE_VERSION: '18'

jobs:

  # ────────────────────────────────────────────────────────────────
  # Job 1: Test — runs on every push and PR
  # ────────────────────────────────────────────────────────────────
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
          cache-dependency-path: frontend/package-lock.json
      - run: cd frontend && npm ci
      - run: cd frontend && npm run test:ci
      - run: cd frontend && npm run lint
      - run: cd frontend && npm run build:prod

  # ────────────────────────────────────────────────────────────────
  # Job 2: Deploy Infrastructure — main branch only, after test
  # ────────────────────────────────────────────────────────────────
  deploy-infrastructure:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v3
      - uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id:     ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region:            ${{ env.AWS_REGION }}
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
          cache-dependency-path: infrastructure/package-lock.json
      - name: Build Lambda packages
        run: |
          pip install -r lambda/authorizer/requirements.txt   -t lambda/authorizer/
          pip install -r lambda/ml-processor/requirements.txt -t lambda/ml-processor/
      - name: Deploy CDK stacks
        run: cd infrastructure && npm ci && npm run build && npx cdk deploy --all --require-approval never
        env:
          CDK_DEFAULT_ACCOUNT: ${{ secrets.AWS_ACCOUNT_ID }}
          ENVIRONMENT:         production

  # ────────────────────────────────────────────────────────────────
  # Job 3: Deploy Frontend — runs after infrastructure is ready
  # ────────────────────────────────────────────────────────────────
  deploy-frontend:
    needs: [test, deploy-infrastructure]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v3
      - uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id:     ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region:            ${{ env.AWS_REGION }}
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
      - run: cd frontend && npm ci && npm run build:prod
      - name: Trigger Amplify release
        run: |
          aws amplify start-job \
            --app-id      ${{ secrets.AMPLIFY_APP_ID }} \
            --branch-name main \
            --job-type    RELEASE

7.3 Environment Promotion Strategy

Promote builds through three stages with explicit quality gates. No code reaches production without passing development and staging validation.

Stage 1
Development
  • Feature development
  • Unit testing
  • Integration testing
  • Code review
Stage 2
Staging
  • End-to-end testing
  • Performance testing
  • Security scanning
  • UAT sign-off
Stage 3
Production
  • Blue-green deploy
  • Health checks
  • Monitoring alerts
  • Rollback capability

deploy.sh — Promote to any environment

sh
scripts/deploy.sh
#!/bin/bash
# Usage: ./scripts/deploy.sh [dev|staging|prod] [region]
set -euo pipefail

ENVIRONMENT=${1:-dev}
REGION=${2:-us-east-1}

echo "Deploying to: $ENVIRONMENT  |  region: $REGION"

# ── Resolve stack name and Amplify App ID per environment ────────
case $ENVIRONMENT in
  dev)     STACK_NAME="MLAppStack-Dev";     AMPLIFY_APP_ID="$DEV_AMPLIFY_APP_ID"     ;;
  staging) STACK_NAME="MLAppStack-Staging"; AMPLIFY_APP_ID="$STAGING_AMPLIFY_APP_ID" ;;
  prod)    STACK_NAME="MLAppStack-Prod";    AMPLIFY_APP_ID="$PROD_AMPLIFY_APP_ID"    ;;
  *)       echo "Error: environment must be dev | staging | prod"; exit 1 ;;
esac

# ── Step 1: Deploy CDK infrastructure ───────────────────────────
echo "Step 1/4: Deploying CDK stack $STACK_NAME ..."
(cd infrastructure && npm run build && npx cdk deploy "$STACK_NAME" --require-approval never)

# ── Step 2: Read stack outputs ───────────────────────────────────
echo "Step 2/4: Reading stack outputs ..."
query() {
  aws cloudformation describe-stacks \
    --stack-name "$STACK_NAME" \
    --query "Stacks[0].Outputs[?OutputKey==\`$1\`].OutputValue" \
    --output text
}
API_URL=$(query ApiGatewayUrl)
USER_POOL_ID=$(query UserPoolId)
CLIENT_ID=$(query UserPoolClientId)
echo "  API URL     : $API_URL"
echo "  User Pool   : $USER_POOL_ID"

# ── Step 3: Write frontend environment file ──────────────────────
echo "Step 3/4: Writing environment file ..."
IS_PROD=$([ "$ENVIRONMENT" = "prod" ] && echo "true" || echo "false")

cat > "frontend/src/environments/environment.${ENVIRONMENT}.ts" << EOF
export const environment = {
  production: ${IS_PROD},
  cognito: {
    userPoolId:          '${USER_POOL_ID}',
    userPoolWebClientId: '${CLIENT_ID}',
    region:              '${REGION}',
  },
  api: {
    baseUrl: '${API_URL}',
    region:  '${REGION}',
  },
};
EOF

# ── Step 4: Build and trigger Amplify release ────────────────────
echo "Step 4/4: Building Angular and triggering Amplify release ..."
(cd frontend && npm run "build:${ENVIRONMENT}")

aws amplify start-job \
  --app-id      "$AMPLIFY_APP_ID" \
  --branch-name "$ENVIRONMENT" \
  --job-type    RELEASE

echo ""
echo "Deployment complete."
echo "  Environment : $ENVIRONMENT"
echo "  API URL     : $API_URL"
echo "  User Pool   : $USER_POOL_ID"
📋 Required GitHub Secrets
  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • AWS_ACCOUNT_ID
  • AMPLIFY_APP_ID
  • DEV_AMPLIFY_APP_ID
  • STAGING_AMPLIFY_APP_ID
  • PROD_AMPLIFY_APP_ID
✅ Pre-deploy Checklist
  • Bedrock model access enabled in Console
  • Cognito domain prefix matches environment.ts
  • CORS origins updated to your Amplify domain
  • dataTraceEnabled: false confirmed
  • Lambda IAM roles scoped to specific ARNs
  • Entra ID tenant ID in SAML metadata URL replaced

8. Best Practices and Considerations

8.1 Security Best Practices

Authentication and Authorization

  • Multi-Factor Authentication: Enforce MFA for all users, especially administrators
  • Token Management: Implement short-lived access tokens (1 hour) with secure refresh token rotation
  • Principle of Least Privilege: Grant minimum necessary permissions to users and services
  • Regular Access Reviews: Conduct quarterly reviews of user permissions and group memberships
  • Session Management: Implement proper session timeout and concurrent session limits

API Security

  • Input Validation: Validate all inputs at API Gateway and Lambda function levels
  • Rate Limiting: Implement aggressive rate limiting to prevent abuse and DDoS attacks
  • Request Size Limits: Set appropriate payload size limits for ML model inputs
  • CORS Configuration: Configure restrictive CORS policies for production environments
  • API Versioning: Implement proper API versioning to maintain backward compatibility

Data Protection

  • Encryption at Rest: Enable encryption for all data stores (S3, DynamoDB, CloudWatch Logs)
  • Encryption in Transit: Enforce TLS 1.2 minimum (CloudFront policy: TLSv1.2_2021); TLS 1.3 negotiated where clients support it. Implement certificate pinning in mobile clients.
  • Key Management: Use AWS KMS with customer-managed keys and implement key rotation
  • Data Classification: Classify data based on sensitivity and apply appropriate protection measures
  • Data Retention: Implement data retention policies and secure deletion procedures

Infrastructure Security

  • Network Segmentation: Use VPCs, private subnets, and security groups for network isolation
  • WAF Rules: Implement comprehensive WAF rules including OWASP Top 10 protection
  • Security Groups: Configure restrictive security groups with minimal required access
  • VPC Endpoints: Use VPC endpoints for AWS service communication to avoid internet routing
  • CloudTrail Logging: Enable comprehensive CloudTrail logging with log file validation

8.2 Performance Optimization

Frontend Optimization

  • Code Splitting: Implement lazy loading for Angular modules and components
  • Bundle Optimization: Use Angular CLI build optimizations and tree shaking
  • Caching Strategy: Implement proper HTTP caching headers and service worker caching
  • Image Optimization: Use WebP format and responsive images with proper sizing
  • CDN Configuration: Optimize CloudFront caching policies and edge locations

API Performance

  • Lambda Optimization: Optimize Lambda function memory allocation and execution time
  • Connection Pooling: Implement connection pooling for database and external service connections
  • Caching: Implement Redis/ElastiCache for frequently accessed data
  • Async Processing: Use SQS/SNS for asynchronous ML model processing
  • Compression: Enable GZIP compression for API responses

ML Service Optimization

  • Model Optimization: Use SageMaker Compilation Jobs (formerly Neo) for model optimization and hardware-specific compilation
  • Endpoint Configuration: Configure appropriate instance types and auto-scaling policies
  • Batch Processing: Implement batch inference for bulk predictions
  • Model Caching: Cache model predictions for identical inputs
  • Multi-Model Endpoints: Use multi-model endpoints for cost optimization

8.3 Monitoring and Alerting

Application Monitoring

  • Real User Monitoring: Implement RUM to track actual user experience and performance
  • Error Tracking: Use CloudWatch Insights and X-Ray for distributed tracing
  • Performance Metrics: Monitor Core Web Vitals and application-specific metrics
  • Custom Dashboards: Create business-specific dashboards for stakeholders
  • Synthetic Monitoring: Implement synthetic tests for critical user journeys

Infrastructure Monitoring

  • Resource Utilization: Monitor CPU, memory, and network utilization across all services
  • Cost Monitoring: Implement cost alerts and budget monitoring for AWS resources
  • Security Monitoring: Monitor for security events and anomalous behavior
  • Availability Monitoring: Track service availability and uptime metrics
  • Capacity Planning: Monitor trends for proactive capacity planning

ML Model Monitoring

  • Model Performance: Monitor model accuracy, latency, and throughput metrics
  • Data Drift Detection: Implement monitoring for input data distribution changes
  • Model Drift Detection: Monitor for model performance degradation over time
  • Prediction Monitoring: Track prediction distributions and anomalies
  • A/B Testing: Implement A/B testing framework for model comparisons

9. Troubleshooting and Monitoring

9.1 Common Issues and Solutions

Authentication Issues

Issue: CORS errors during authentication

Symptoms: Browser console shows CORS policy errors

Cause: Incorrect CORS configuration in API Gateway or Cognito

Solution:

  • • Verify API Gateway CORS settings include all required headers
  • • Check Cognito App Client settings for allowed callback URLs
  • • Ensure preflight OPTIONS requests are handled correctly
  • • Validate that the frontend domain is whitelisted

Issue: JWT token validation failures

Symptoms: 401 Unauthorized errors from Lambda Authorizer

Cause: Token expiration, invalid signature, or incorrect claims

Solution:

  • • Check token expiration time and implement refresh logic
  • • Verify JWKS endpoint is accessible and keys are current
  • • Validate audience and issuer claims match configuration
  • • Check CloudWatch logs for detailed error messages

API Gateway Issues

Issue: API Gateway timeout errors

Symptoms: 504 Gateway Timeout errors

Cause: Lambda function execution time exceeds API Gateway timeout

Solution:

  • • Optimize Lambda function performance and memory allocation
  • • Implement asynchronous processing for long-running tasks
  • • Use SQS/SNS for decoupling and background processing
  • • Consider using Step Functions for complex workflows

Issue: Rate limiting and throttling

Symptoms: 429 Too Many Requests errors

Cause: Exceeded API Gateway or Lambda concurrency limits

Solution:

  • • Review and adjust API Gateway throttling settings
  • • Implement exponential backoff in client applications
  • • Monitor Lambda concurrent executions and request limits
  • • Consider implementing request queuing for peak loads

ML Service Issues

Issue: SageMaker endpoint errors

Symptoms: Model inference failures or high latency

Cause: Endpoint configuration, model issues, or resource constraints

Solution:

  • • Check endpoint status and health in SageMaker console
  • • Verify model artifacts and container configuration
  • • Monitor endpoint metrics and auto-scaling settings
  • • Validate input data format and preprocessing steps

Issue: Bedrock model access errors

Symptoms: Access denied or model not available errors

Cause: Insufficient permissions or model not enabled in region

Solution:

  • • Verify Bedrock model access is enabled in AWS console
  • • Check IAM permissions for bedrock:InvokeModel action
  • • Confirm model availability in the deployment region
  • • Review model-specific usage quotas and limits

9.2 Monitoring Setup CloudWatch

Monitoring is split into two parts: a CloudWatch Dashboard that visualises key metrics across all services, and a CDK MonitoringStack that provisions SNS-backed alarms so the team is paged before issues become outages.

  1. DashboardFour metric widgets (API Gateway, Lambda Authorizer, ML Processor, SageMaker) plus two log insights widgets for recent errors.
  2. SNS Alert TopicA single SNS topic with an email subscription. All alarms route here — swap to PagerDuty/Slack via additional subscriptions if needed.
  3. AlarmsFour alarms covering API 4XX error rate, Lambda error count, SageMaker latency, and estimated monthly AWS spend.

CloudWatch Dashboard — widget definitions

How to deploy this dashboard Paste the JSON below into CloudWatch → Dashboards → Create dashboard → JSON, or reference it in your CDK stack via new cloudwatch.Dashboard() with dashboardBody: JSON.stringify(widgetDefs).
{ }
cloudwatch/dashboard.json
{
  "widgets": [
    {
      "type": "metric",
      "properties": {
        "title": "API Gateway — Request & Error Rates",
        "metrics": [
          ["AWS/ApiGateway", "Count",    "ApiName", "ML App API"],
          [".",              "Latency",  ".",        "."         ],
          [".",              "4XXError", ".",        "."         ],
          [".",              "5XXError", ".",        "."         ]
        ],
        "period": 300,
        "stat": "Sum",
        "region": "us-east-1"
      }
    },
    {
      "type": "metric",
      "properties": {
        "title": "Lambda Authorizer — Invocations & Health",
        "metrics": [
          ["AWS/Lambda", "Invocations", "FunctionName", "AuthorizerFunction"],
          [".",          "Duration",    ".",             "."                ],
          [".",          "Errors",      ".",             "."                ],
          [".",          "Throttles",   ".",             "."                ]
        ],
        "period": 300,
        "stat": "Sum",
        "region": "us-east-1"
      }
    },
    {
      "type": "metric",
      "properties": {
        "title": "ML Processor Lambda — Concurrency & Duration",
        "metrics": [
          ["AWS/Lambda", "Invocations",         "FunctionName", "MLProcessorFunction"],
          [".",          "Duration",            ".",             "."                 ],
          [".",          "Errors",              ".",             "."                 ],
          [".",          "ConcurrentExecutions",".",             "."                 ]
        ],
        "period": 300,
        "stat": "Average",
        "region": "us-east-1"
      }
    },
    {
      "type": "metric",
      "properties": {
        "title": "SageMaker Endpoint — Latency & Errors",
        "metrics": [
          ["AWS/SageMaker/Endpoints", "Invocations",        "EndpointName", "ml-model-endpoint"],
          [".",                       "ModelLatency",       ".",             "."               ],
          [".",                       "OverheadLatency",    ".",             "."               ],
          [".",                       "Invocation4XXErrors",".",             "."               ],
          [".",                       "Invocation5XXErrors",".",             "."               ]
        ],
        "period": 300,
        "stat": "Sum",
        "region": "us-east-1"
      }
    },
    {
      "type": "log",
      "properties": {
        "title": "Recent Authorization Errors",
        "query": "SOURCE '/aws/lambda/AuthorizerFunction'\n| fields @timestamp, @message\n| filter @message like /ERROR/\n| sort @timestamp desc\n| limit 100",
        "region": "us-east-1"
      }
    },
    {
      "type": "log",
      "properties": {
        "title": "ML Processing Errors & Timeouts",
        "query": "SOURCE '/aws/lambda/MLProcessorFunction'\n| fields @timestamp, @message, @requestId\n| filter @message like /ERROR/ or @message like /TIMEOUT/\n| sort @timestamp desc\n| limit 100",
        "region": "us-east-1"
      }
    }
  ]
}

CloudWatch Alarms — CDK MonitoringStack

Replace the placeholder email address Substitute [email protected] with your actual on-call address before deploying. For Slack or PagerDuty routing, add an HTTPS subscription to the SNS topic instead of (or alongside) the email subscription.
TS
infrastructure/lib/monitoring-stack.ts
import * as cdk           from 'aws-cdk-lib';
import * as cloudwatch    from 'aws-cdk-lib/aws-cloudwatch';
import * as sns           from 'aws-cdk-lib/aws-sns';
import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';
import { Construct }      from 'constructs';

export class MonitoringStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ── SNS Alert Topic ───────────────────────────────────────────
    const alertTopic = new sns.Topic(this, 'MLAppAlerts', {
      displayName: 'ML App Monitoring Alerts',
    });
    // Replace with your ops team address (or add SNS→Slack/PagerDuty subscription)
    alertTopic.addSubscription(
      new subscriptions.EmailSubscription('[email protected]')
    );

    // Helper: create an alarm and wire it to the SNS topic
    const alarm = (
      id: string, name: string, desc: string,
      metric: cloudwatch.Metric,
      threshold: number, periods: number,
    ) =>
      new cloudwatch.Alarm(this, id, {
        alarmName:          name,
        alarmDescription:   desc,
        metric,
        threshold,
        evaluationPeriods:  periods,
        comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
        treatMissingData:   cloudwatch.TreatMissingData.NOT_BREACHING,
      }).addAlarmAction(new cloudwatch.SnsAction(alertTopic));


    // ── Alarm 1: API Gateway 4XX error rate ───────────────────────
    alarm(
      'APIGatewayErrorRate',
      'ML-App-API-4XX-Rate',
      'API Gateway client error rate exceeded threshold',
      new cloudwatch.Metric({
        namespace:     'AWS/ApiGateway',
        metricName:    '4XXError',
        dimensionsMap: { ApiName: 'ML App API' },
        statistic:     'Sum',
        period:        cdk.Duration.minutes(5),
      }),
      10,   // > 10 errors per 5-min window
      2,    // for 2 consecutive periods
    );

    // ── Alarm 2: ML Processor Lambda error count ──────────────────
    alarm(
      'LambdaErrorRate',
      'ML-App-Lambda-Errors',
      'ML Processor Lambda function error count is too high',
      new cloudwatch.Metric({
        namespace:     'AWS/Lambda',
        metricName:    'Errors',
        dimensionsMap: { FunctionName: 'MLProcessorFunction' },
        statistic:     'Sum',
        period:        cdk.Duration.minutes(5),
      }),
      5,    // > 5 errors per 5-min window
      2,
    );

    // ── Alarm 3: SageMaker endpoint model latency ─────────────────
    alarm(
      'SageMakerLatency',
      'ML-App-SageMaker-Latency',
      'SageMaker model latency exceeded 5 s',
      new cloudwatch.Metric({
        namespace:     'AWS/SageMaker/Endpoints',
        metricName:    'ModelLatency',
        dimensionsMap: { EndpointName: 'ml-model-endpoint' },
        statistic:     'Average',
        period:        cdk.Duration.minutes(5),
      }),
      5000, // 5,000 ms = 5 seconds
      3,
    );

    // ── Alarm 4: Estimated AWS monthly spend ──────────────────────
    alarm(
      'CostAlarm',
      'ML-App-Cost-Alert',
      'Estimated monthly AWS charges exceeded budget',
      new cloudwatch.Metric({
        namespace:     'AWS/Billing',
        metricName:    'EstimatedCharges',
        dimensionsMap: { Currency: 'USD' },
        statistic:     'Maximum',
        period:        cdk.Duration.hours(6),
      }),
      1000, // $1,000 USD monthly budget — adjust to your limit
      1,
    );
  }
}
📊 Alarm Thresholds Summary
  • API 4XX errors — >10 per 5 min, 2 periods
  • Lambda errors — >5 per 5 min, 2 periods
  • SageMaker latency — >5,000 ms avg, 3 periods
  • Monthly spend — >$1,000 USD
✅ Deployment Steps
  • Replace placeholder email in stack
  • Add MonitoringStack to infrastructure.ts
  • Run cdk deploy MonitoringStack
  • Confirm SNS subscription email
  • Import dashboard.json in CloudWatch Console

10. Appendix

10.1 Reference Links

AWS Documentation

  • AWS Amplify: https://docs.aws.amazon.com/amplify/
  • Amazon Cognito: https://docs.aws.amazon.com/cognito/
  • API Gateway: https://docs.aws.amazon.com/apigateway/
  • AWS Lambda: https://docs.aws.amazon.com/lambda/
  • Amazon SageMaker: https://docs.aws.amazon.com/sagemaker/
  • Amazon Bedrock: https://docs.aws.amazon.com/bedrock/
  • AWS CDK: https://docs.aws.amazon.com/cdk/
  • AWS WAF: https://docs.aws.amazon.com/waf/

Angular and Frontend

  • Angular Documentation: https://angular.io/docs
  • Angular Material: https://material.angular.io/
  • AWS Amplify JavaScript: https://docs.amplify.aws/javascript/
  • TypeScript Handbook: https://www.typescriptlang.org/docs/
  • RxJS Documentation: https://rxjs.dev/guide/overview

Security and Compliance

  • OAuth 2.0 Specification: https://tools.ietf.org/html/rfc6749
  • OpenID Connect: https://openid.net/connect/
  • JWT Specification: https://tools.ietf.org/html/rfc7519
  • OWASP Top 10: https://owasp.org/www-project-top-ten/
  • AWS Security Best Practices: https://aws.amazon.com/security/security-resources/

Machine Learning

  • SageMaker Examples: https://github.com/aws/amazon-sagemaker-examples
  • Bedrock User Guide: https://docs.aws.amazon.com/bedrock/latest/userguide/
  • ML Best Practices: https://aws.amazon.com/machine-learning/ml-best-practices/
  • Model Deployment Patterns: https://ml-ops.org/

10.2 Glossary

API Gateway: AWS service for creating, publishing, maintaining, monitoring, and securing REST and WebSocket APIs
AWS Amplify: Development platform for building secure, scalable mobile and web applications
Amazon Bedrock: Fully managed service for accessing foundation models via APIs
CDK (Cloud Development Kit): Framework for defining cloud infrastructure using familiar programming languages
CloudFront: AWS content delivery network (CDN) service for fast content delivery
Cognito: AWS service for user authentication, authorization, and user management
CORS (Cross-Origin Resource Sharing): Mechanism that allows restricted resources on a web page to be requested from another domain
JWT (JSON Web Token): Compact, URL-safe means of representing claims to be transferred between two parties
Lambda: AWS serverless compute service that runs code in response to events
RBAC (Role-Based Access Control): Method of regulating access to resources based on the roles of individual users
SageMaker: AWS machine learning platform for building, training, and deploying ML models
SAML (Security Assertion Markup Language): XML-based standard for exchanging authentication and authorization data
SPA (Single Page Application): Web application that loads a single HTML page and dynamically updates content
WAF (Web Application Firewall): Security service that protects web applications from common web exploits

10.3 Version History

Version Date Author Changes
1.0 July 2025 Technical Documentation Team Initial release with complete implementation guide
0.9 June 2025 Technical Documentation Team Beta release for internal review and testing
0.8 May 2025 Technical Documentation Team Added security architecture and compliance sections
0.7 April 2025 Technical Documentation Team Completed implementation guide and deployment sections
0.6 March 2025 Technical Documentation Team Added data flow architecture and ML integration details
0.5 February 2025 Technical Documentation Team Initial architecture overview and authentication flows

Document Series Navigation

Part 1 AWS Amplify Hosting for ML/AI Services Guide Architecture · Access Patterns · Security Design
Part 2 — You are here AWS Amplify ML Services Integration Guide CDK Stacks · Lambda Code · CI/CD · Deployment
Back to AWS Amplify Hosting for ML/AI Services Guide