AWS
stephen-ade.github.io
AWS Amplify Hosting for ML/AI Services Guide
Technical Guide
AWS Architecture Guide
· Part 1 of 2

AWS Amplify Hosting for ML/AI Services Guide

Architecture and access patterns for enterprise Angular SPAs accessing AWS ML services with multi-layer security. For CDK stacks, Lambda code, and CI/CD pipelines, see Part 2.

Version 1.1
Updated March 2026
Audience Solution Architects · Cloud Engineers · DevOps
Covers Amplify · Cognito · API GW · SageMaker · Bedrock
Two-part series. This guide covers architecture & access patterns. For the full implementation reference with CDK, Lambda, and CI/CD, see the companion guide. Part 2: ML Services Integration Guide
Section 1

1Introduction and Overview

Purpose and Scope

This comprehensive guide provides a detailed roadmap for designing, developing, integrating, and deploying AWS Amplify hosting for Single Page Applications (SPAs) that enable corporate users to access AWS Machine Learning services. The guide focuses on establishing a secure, scalable, and efficient architecture that integrates with enterprise identity systems while leveraging AWS's powerful ML offerings including SageMaker, Bedrock, and Lambda processors for AI capabilities.

This guide serves as a blueprint for organizations looking to provide their employees with secure access to advanced AI capabilities while maintaining corporate security standards and governance requirements.

The scope encompasses:

  • Front-end development using the Angular framework
  • Authentication via Entra ID integration through AWS Cognito
  • Secure API access to internal ML endpoints
  • Multi-layered security architecture
  • Implementation and deployment workflows
  • Best practices for corporate environments

Target Audience

This guide is intended for the following roles. Readers should have intermediate to advanced knowledge of AWS services, authentication mechanisms, and modern web application development practices.

  • Solution Architects responsible for designing ML service access solutions
  • Cloud Engineers implementing AWS services and security controls
  • Full Stack Developers working on Angular SPAs and AWS integrations
  • DevOps Engineers managing deployments and CI/CD pipelines
  • Security Engineers ensuring compliance with corporate security requirements
  • IT Managers overseeing the implementation of AI capabilities within their organization

Prerequisites

To successfully implement the solutions described in this guide, the following prerequisites are necessary.

Knowledge Prerequisites

  • Understanding of Angular framework and SPA development
  • Familiarity with AWS services, particularly Amplify, Cognito, API Gateway, Lambda, SageMaker, and Bedrock
  • Knowledge of authentication protocols (OAuth 2.0, OIDC) and JWT token validation
  • Experience with cloud security best practices
  • Basic understanding of ML/AI service consumption patterns

Technical Prerequisites

  • AWS account with appropriate permissions to create and manage required services
  • Entra ID (formerly Azure AD) tenant with administrative access
  • Node.js and Angular development environment
  • AWS CLI and Amplify CLI configured locally
  • Access to internal ML model endpoints and permissions to AWS Bedrock services
  • Git repository for source code management
Bedrock Model Access Requirement Amazon Bedrock foundation models are not enabled by default in any AWS account. Each model must be explicitly requested and approved before use. Navigate to Amazon Bedrock → Model Access → Manage model access in the AWS Console to request access to the models you need (e.g. Anthropic Claude, Meta Llama, Amazon Nova). Model availability also varies by AWS region. Allow up to 24 hours for approval of third-party models.
Scope Note This guide assumes that ML models are already developed and deployed to SageMaker endpoints or accessible via Bedrock APIs. The focus is on building the frontend application and the secure access architecture.

Architecture Benefits

🛡
Security
  • Multi-layer defense at each tier
  • Edge protection via WAF + CloudFront
  • Entra ID + Cognito federation
  • Lambda authorizer token validation
  • Fine-grained IAM access control
  • Encrypted data throughout
Performance
  • Global CloudFront edge delivery
  • Static asset caching
  • Optimized API routing
  • Scalable Amplify hosting
  • Async ML service consumption
  • Serverless Lambda scalability
Operational
  • Built-in CI/CD with Amplify
  • Simplified UAT/PROD deployments
  • Centralized monitoring & logging
  • Automated demand-based scaling
  • Managed service overhead reduction
📈
Business
  • Democratized ML/AI access
  • Corporate identity UX consistency
  • Serverless cost optimization
  • Accelerated AI feature delivery
  • Enterprise-grade compliance
Section 2

2Architecture Overview

High-Level Architecture Diagrams

Overall System Architecture

Corporate User
App Delivery
Amplify Hosted SPA
Angular — runs in browser
CloudFront + WAF
Edge delivery & protection
Authentication
AWS Cognito
User Pool + Federation
Entra ID
Corporate IdP (SAML)
SPA attaches JWT to API request
API Layer
WAF
API Protection
Regional API Gateway
Lambda Authorizer
ML Services Layer
Lambda Processors
ML Orchestration
SageMaker Endpoints
Bedrock Models

Figure 1: Overall System Architecture

Network Security Architecture

Internet
WAF (Edge Protection)
CloudFront
CDN + SSL Termination
Amplify Hosting (SPA)
API Gateway WAF
2nd WAF Layer
Regional API Gateway
Internal AWS Network (VPC)
Lambda
SageMaker
Bedrock API Access

Figure 2: Network Security Architecture

Component Descriptions

The architecture consists of the following key components, each serving a specific purpose in the overall system.

ComponentDescriptionRole in Architecture
AWS Amplify HostingFully managed service for hosting web applicationsHosts the Angular SPA with built-in CI/CD, global CDN, and HTTPS
Angular SPASingle Page Application built with Angular frameworkProvides the user interface for accessing ML services
Amazon CloudFrontContent delivery network serviceDistributes application content globally and integrates with WAF
AWS WAFWeb Application FirewallProtects against common web exploits at both edge and API levels
Entra IDMicrosoft's cloud identity service (formerly Azure AD)Corporate identity provider for user authentication
Amazon CognitoUser authentication and authorization serviceIdentity federation with Entra ID and token issuance for AWS services
API GatewayManaged service for creating, publishing, and securing APIsEntry point for backend ML service access with WAF protection
Lambda AuthorizerCustom authorization logic for API GatewayValidates JWT tokens and assigns IAM policies for backend access
Lambda ProcessorsServerless compute for processing ML requestsCalls Bedrock Foundational Model APIs and processes responses
Amazon SageMakerFully managed ML serviceHosts custom internal ML models with endpoint access
AWS BedrockFully managed foundation model serviceProvides access to foundation models from Anthropic (Claude), Amazon (Titan, Nova), Meta (Llama), Mistral AI, Cohere, AI21 Labs, Stability AI, and Writer. Note: OpenAI models are not available on Amazon Bedrock.
IAM RolesIdentity and Access Management rolesControls permissions for accessing AWS services and resources

AWS Amplify CloudFront Architecture

AWS Amplify Hosting automatically provisions and configures a CloudFront distribution to serve your SPA, providing several integration benefits.

☁ CloudFront Integration Features
  • Global content delivery via edge locations
  • Automatic cache management for static assets
  • HTTPS enforcement with managed SSL certificates
  • Edge computing for optimized delivery
  • AWS WAF integration for edge security
  • Customizable cache behaviors and TTL settings
🔒 Security Enhancements
  • WAF Web ACL associations for threat protection
  • Geo-restriction capabilities
  • Field-level encryption for sensitive data
  • Origin access identity for S3 backend protection
  • Custom HTTP security headers
  • DDoS protection via AWS Shield

CloudFront Configuration for SPA

The CloudFront distribution configured by Amplify is optimized for Single Page Applications with the following settings.

JSON
{
  "Origin": {
    "Domain": "amplifyapp.com",
    "ID": "amplify-hosted-app",
    "OriginPath": "/prod/[appId]"
  },
  "DefaultCacheBehavior": {
    "ViewerProtocolPolicy": "redirect-to-https",
    "AllowedMethods": ["GET", "HEAD", "OPTIONS"],
    "DefaultTTL": 86400,
    "MaxTTL": 31536000,
    "FunctionAssociations": [
      { "EventType": "viewer-request", "FunctionARN": "arn:aws:cloudfront::function:SPA-Router" }
    ]
  },
  "CustomErrorResponses": [
    { "ErrorCode": 404, "ResponseCode": 200, "ResponsePagePath": "/index.html" }
  ],
  "WebACLId": "arn:aws:wafv2:us-east-1:[account]:global/webacl/AmplifyAppProtection/[id]",
  "Enabled": true,
  "PriceClass": "PriceClass_All"
}
  • SPA routing support via custom 404→200 redirect to index.html
  • HTTPS enforcement with redirect-to-https viewer protocol policy
  • Optimized caching strategy for static assets
  • WAF integration through WebACLId
  • Global distribution through PriceClass_All

API Gateway and WAF Architecture

While the frontend is protected by CloudFront + WAF, the backend ML services require an additional protection layer provided by a dedicated WAF-protected API Gateway with Lambda authorization.

Angular SPA
User's Browser
API Gateway Protection Layer
WAF Web ACL
Regional API Gateway
Lambda Authorizer
JWT Validation
ML Services

Figure 3: API Gateway and WAF Architecture

Private API Gateway

The Private API Gateway is configured to:

  • Restrict access to internal network and authorized clients only
  • Use resource policies to enforce access control
  • Integrate with Lambda Authorizers for token validation
  • Implement endpoint routing to appropriate ML services
  • Enable detailed API request logging and monitoring
  • Implement request throttling to prevent abuse

WAF Configuration

The WAF Web ACL for the API Gateway includes:

  • Rate-based rules to prevent DDoS attacks
  • IP-based access control rules
  • SQL injection protection
  • Cross-site scripting (XSS) protection
  • Geo-matching rules to restrict access by location
  • Custom rules for specific security requirements
Important: Private API Gateway vs. Browser-Based SPA A Private API Gateway endpoint type ("types": ["PRIVATE"]) is accessible only from within a VPC — not from a public browser. An Amplify-hosted Angular SPA runs in the user's browser outside any VPC, so it cannot directly call a Private API Gateway endpoint. Choose one of the following patterns to resolve this:
  • Regional API Gateway (recommended for browser SPAs): Use a Regional endpoint type protected by WAF, Cognito authorizer, and resource policies.
  • VPC-peered proxy: Place an NLB or ALB inside a VPC to proxy requests from CloudFront to the Private API Gateway via a VPC Endpoint.
  • AWS PrivateLink + CloudFront Origin: Route CloudFront to an internal origin using PrivateLink, keeping the API truly private.
Ensure your endpoint type matches your actual network topology before deploying.

API Gateway Endpoint Configuration

JSON
{
  "apiId": "abc123def456",
  "endpointConfiguration": {
    "types": ["PRIVATE"],
    "vpcEndpointIds": ["vpce-0abc123def456789"]
  },
  "policy": {
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": "*",
      "Action": "execute-api:Invoke",
      "Resource": "arn:aws:execute-api:region:account-id:api-id/*",
      "Condition": {
        "StringEquals": { "aws:SourceVpce": "vpce-0abc123def456789" }
      }
    }]
  }
}

Architecture Layers

The solution is organized into four distinct layers, promoting separation of concerns, better security, and easier maintenance.

⬡ Frontend Layer
  • AWS Amplify Hosting — Managed hosting with built-in CI/CD capabilities
  • Angular SPA — User interface for accessing ML services
  • Amplify Libraries — Client-side auth, API, and AWS service integrations
  • CloudFront Distribution — Automatically configured for content delivery and edge security
  • SSL/TLS Termination — HTTPS enforcement with managed certificates
Key Responsibilities
  • User interface presentation and interaction
  • Client-side authentication flow handling
  • Token management and secure storage
  • API requests and response rendering
🔑 Authentication Layer
  • Azure Entra ID — Enterprise identity provider, handles user authentication and directory
  • AWS Cognito User Pool — Identity federation with Entra ID and JWT token issuance
  • Identity Federation — OIDC-based integration between Entra ID and Cognito
  • IAM Roles — Authorization and access control for AWS resources
Key Responsibilities
  • User authentication through corporate credentials
  • Identity federation between Entra ID and AWS
  • JWT token issuance, management, and refresh
  • Role-based access control
🌐 API Layer
  • AWS WAF — Web Application Firewall at CloudFront and API Gateway levels
  • API Gateway — Managed API service configured for private access
  • Lambda Authorizers — Custom JWT validation and IAM permission assignment
  • API Routes — Endpoint definitions for different ML services
Key Responsibilities
  • Request routing to appropriate backend services
  • Token validation and authorization
  • Rate limiting, throttling, and threat protection
  • Request/response transformation and logging
🤖 ML Services Layer
  • SageMaker Endpoints — Managed hosting for custom internal ML models
  • Bedrock Foundation Models — Access to models from Anthropic, Amazon, Meta, Mistral, Cohere, and others
  • Lambda Processors — Serverless functions for Bedrock API requests and business logic
  • Model Monitoring — Performance and usage tracking for ML models
Key Responsibilities
  • ML model inference and processing
  • Request transformation for model compatibility
  • Response processing and formatting
  • Usage tracking, quota management, and error handling
Section 3

3Authentication and Authorization Flows

Authentication Flow Diagrams

User Authentication Flow

User
Angular SPA
Amplify Hosted
Amplify Auth Library
signInWithRedirect()
AWS Cognito
Hosted UI
Entra ID Login Page
SAML redirect
Entra ID
Credential validation
Cognito
SAML assertion received
Cognito Token Issuance
Access + ID + Refresh tokens
JWT Tokens → SPA
via /auth/callback redirect
Token Storage in SPA
Amplify manages token cache

Figure 4: User Authentication Flow

API Authorization Flow

Angular SPA
Authenticated user
API Request + JWT Token
Authorization: Bearer <access_token>
WAF Rules Check
Block malicious traffic
Regional API Gateway
Route to Lambda Authorizer
Lambda Authorizer
Verify RS256 signature · validate claims · check cognito:groups
Allow → IAM Policy
User in Administrators or MLUsers
ML Service Processing
Lambda Processor invoked

Figure 5: API Authorization Flow

JWT Token Validation Process

The Lambda Authorizer performs a multi-step validation process on every incoming JWT access token before granting access to ML services.

  1. Extract Token — Remove the Bearer prefix from the Authorization header
  2. Decode Header — Decode the JWT header without verification to extract the kid (key ID)
  3. Fetch Public Key — Retrieve the matching RSA public key from Cognito's JWKS endpoint
  4. Verify Signature — Verify the token's RS256 signature using the public key
  5. Validate Claims — Check expiration (exp), issue time (iat), issuer (iss). For Cognito access tokens, validate client_id (not aud) and confirm token_use === "access"
  6. Check Group Membership — Read group membership from the cognito:groups claim and verify required groups
  7. Generate IAM Policy — Return an Allow or Deny policy to API Gateway with enriched user context
Cognito Access Token Claims Cognito access tokens use client_id rather than the standard aud claim for audience identification. Group membership is stored under cognito:groups (not groups). Configuring JWT libraries with audience: CLIENT_ID will cause InvalidAudienceError on access tokens — disable audience verification in the library and validate client_id manually.

Sample JWT Access Token Payload

JSON — Cognito Access Token Payload
{
  "sub": "user123",
  "iss": "https://cognito-idp.region.amazonaws.com/us-east-1_example",
  "client_id": "clientidexample",
  "token_use": "access",
  "scope": "openid profile email",
  "auth_time": 1684858239,
  "exp": 1684861839,
  "iat": 1684858239,
  "username": "[email protected]",
  "cognito:groups": ["ML_Users", "Data_Scientists"]
}

Lambda Authorizer Implementation

JavaScript — Lambda Authorizer
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
  jwksUri: `https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL_ID}/.well-known/jwks.json`,
  cache: true,
  cacheMaxAge: 600000  // 10 minutes
});

const getSigningKey = (kid) => new Promise((resolve, reject) => {
  client.getSigningKey(kid, (err, key) => {
    if (err) return reject(err);
    resolve(key.publicKey || key.rsaPublicKey);
  });
});

// Generate an IAM policy document for API Gateway
const generatePolicy = (principalId, effect, resource, context = {}) => ({
  principalId,
  policyDocument: {
    Version: '2012-10-17',
    Statement: [{ Action: 'execute-api:Invoke', Effect: effect, Resource: resource }]
  },
  context
});

// Return true if user belongs to an allowed group for this method
const determineAccess = (groups, methodArn) => {
  const allowedGroups = ['Administrators', 'MLUsers'];
  return groups.some(g => allowedGroups.includes(g));
};

exports.handler = async (event) => {
  try {
    const token = event.authorizationToken.replace('Bearer ', '');
    const decoded = jwt.decode(token, { complete: true });
    if (!decoded) throw new Error('Invalid token format');

    const signingKey = await getSigningKey(decoded.header.kid);

    // Cognito ACCESS tokens use client_id, not aud — omit audience option
    const verifiedToken = jwt.verify(token, signingKey, {
      issuer: `https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL_ID}`,
      // audience omitted intentionally — Cognito access tokens use client_id
    });

    // Manually validate client_id (replaces standard audience check)
    if (verifiedToken.client_id !== process.env.CLIENT_ID) {
      throw new Error('Invalid client_id');
    }

    // Group membership is in cognito:groups — NOT groups
    const userGroups = verifiedToken['cognito:groups'] || [];
    const allowed = determineAccess(userGroups, event.methodArn);

    return generatePolicy(
      verifiedToken.sub,
      allowed ? 'Allow' : 'Deny',
      event.methodArn,
      { username: verifiedToken.username || verifiedToken.sub, groups: userGroups.join(',') }
    );
  } catch (error) {
    console.error('Authorization error:', error);
    throw new Error('Unauthorized');
  }
};
Section 4

4Data Flow Architecture

End-to-End Data Flow

The end-to-end data flow describes how data travels through the system, from the initial user request to the final ML response. The following steps walk through the complete journey.

  1. Initial Request — User accesses the SPA through a web browser
  2. Content Delivery — CloudFront delivers the SPA application files from the nearest edge location
  3. Authentication — User authenticates through Entra ID via Cognito federation and receives JWT tokens
  4. Local Interaction — User interacts with the Angular SPA interface to submit an ML request
  5. API Request Preparation — SPA prepares the request payload and attaches the JWT access token to the Authorization header
  6. WAF Filtering — The API Gateway WAF evaluates the request against rule sets; malicious traffic is rejected
  7. Lambda Authorization — Lambda Authorizer validates the JWT, checks group membership, and generates an IAM policy
  8. Backend Routing — Depending on the request type: either a direct SageMaker endpoint call (custom models) or a Lambda Processor invocation (Bedrock models)
  9. Response Processing — ML service responses are processed, formatted, and returned through API Gateway
  10. Result Delivery — Results are displayed in the Angular SPA for the user

Data Transformation Points

Transformation PointTransformation TypePurpose
Client-side (Angular SPA)User input formattingPrepare data in format suitable for API requests
API GatewayRequest/response mappingTransform between frontend and backend data formats
Lambda ProcessorModel-specific formattingTransform generic requests into Converse API format for Bedrock
SageMakerModel inference transformationRaw model output processed into structured response
BedrockFoundation model responseStandardized response via Converse API across all model families

SageMaker Integration Flow

For custom internal ML models, requests are routed directly to SageMaker real-time inference endpoints.

API Gateway
Lambda Processor
SageMaker Real-Time Endpoint
Model Inference Response

Figure 6: SageMaker Integration Flow

Synchronous vs. Asynchronous Invocation API Gateway enforces a hard 29-second integration timeout for synchronous calls. Lambda functions can run up to 15 minutes when invoked asynchronously (via SQS, EventBridge, or direct async invocation). For ML inference tasks that may exceed 28 seconds, implement an asynchronous pattern: return a Job ID immediately, then poll for results via a separate status endpoint.

Bedrock Integration Flow

For foundation model access, a Lambda Processor invokes the Bedrock Converse API, which provides a unified interface across all supported model families.

API Gateway
Lambda Processor
Bedrock Converse API
Anthropic Claude
Amazon Nova
Meta Llama 3

Figure 7: Bedrock Integration Flow

Bedrock Lambda Processor

JavaScript — Bedrock Converse API
const { BedrockRuntimeClient, ConverseCommand } = require("@aws-sdk/client-bedrock-runtime");

const bedrockClient = new BedrockRuntimeClient({ region: process.env.AWS_REGION || "us-east-1" });

exports.handler = async (event) => {
  const { prompt, model = "anthropic.claude-3-5-sonnet-20241022-v2:0", maxTokens = 1000, temperature = 0.7, systemPrompt } = JSON.parse(event.body);

  const params = {
    modelId: model,
    messages: [{ role: "user", content: [{ text: prompt }] }],
    inferenceConfig: { maxTokens, temperature, topP: 0.9 }
  };
  if (systemPrompt) params.system = [{ text: systemPrompt }];

  const response = await bedrockClient.send(new ConverseCommand(params));
  const completion = response.output?.message?.content?.[0]?.text || "";

  return {
    statusCode: 200,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model,
      completion,
      stopReason: response.stopReason,
      usage: {
        input_tokens:  response.usage?.inputTokens  || 0,
        output_tokens: response.usage?.outputTokens || 0,
        total_tokens: (response.usage?.inputTokens  || 0) + (response.usage?.outputTokens || 0)
      }
    })
  };
};

Supported Bedrock Model Families

Model FamilyConverse APIRecommended Model IDNotes
Anthropic Claude 3/3.5Supportedanthropic.claude-3-5-sonnet-20241022-v2:0Use Converse API. Legacy Human/Assistant format deprecated.
Amazon Nova / TitanSupportedamazon.nova-pro-v1:0Nova is current-gen; Titan Text is legacy but available.
Meta Llama 3Supportedmeta.llama3-70b-instruct-v1:0Open-weight; Llama 3 replaces Llama 2 on Bedrock.
Mistral AISupportedmistral.mistral-large-2402-v1:0Strong multilingual performance.
AI21 JambaSupportedai21.jamba-1-5-large-v1:0Replaces the Jurassic series.
Model Access Must Be Explicitly Enabled Foundation model access is disabled by default. Go to Amazon Bedrock → Model Access → Manage model access to enable each model you intend to use. Availability varies by region.
Section 5

5Security Architecture

Multi-Layer Security Model

The architecture implements defense-in-depth with security controls at every layer. No single point of failure can expose the ML services backend.

LayerControlsThreats Mitigated
Edge (CloudFront + WAF)WAF Web ACL, geo-restrictions, rate limiting, DDoS protectionBots, DDoS, SQL injection, XSS, geo-based attacks
Identity (Cognito + Entra ID)OIDC federation, MFA, short-lived JWT tokens, session managementCredential theft, unauthorized access, account takeover
API (API Gateway + Lambda Authorizer)JWT validation, IAM policy generation, request throttling, input validationToken replay, privilege escalation, API abuse
Compute (Lambda)Least-privilege IAM roles, VPC isolation, encrypted environment variablesLateral movement, data exfiltration, over-privileged access
ML Services (SageMaker + Bedrock)VPC endpoints, IAM resource policies, CloudWatch monitoringUnauthorized model access, data leakage, cost abuse
🛡 WAF Rule Sets
  • AWS Managed Rules — Core Rule Set (CRS)
  • AWS Managed Rules — Known Bad Inputs
  • Rate-based rules (per IP threshold)
  • Geo-match rules for regional restrictions
  • Custom rules for application-specific patterns
  • IP set allow/deny lists
🔐 IAM Least Privilege
  • Lambda Authorizer: read-only Cognito JWKS access
  • Lambda Processors: specific SageMaker and Bedrock ARN permissions only
  • No wildcard * resource permissions in production
  • Separate execution roles per Lambda function
  • Regular IAM access reviews via Access Analyzer

JWT Token Security Implementation

Token Validation in Python (Lambda Authorizer)

Python — JWT Validation
import jwt, requests, os
from functools import lru_cache

REGION     = os.environ['COGNITO_REGION']
POOL_ID    = os.environ['USER_POOL_ID']
CLIENT_ID  = os.environ['CLIENT_ID']
JWKS_URL   = f'https://cognito-idp.{REGION}.amazonaws.com/{POOL_ID}/.well-known/jwks.json'

@lru_cache(maxsize=1)
def get_jwks():
    return requests.get(JWKS_URL, timeout=10).json()

def get_public_key(kid):
    for key in get_jwks()['keys']:
        if key['kid'] == kid:
            return jwt.algorithms.RSAAlgorithm.from_jwk(key)
    raise ValueError(f'Key not found: {kid}')

def validate_token(token):
    header = jwt.get_unverified_header(token)
    public_key = get_public_key(header['kid'])

    # Cognito access tokens use client_id not aud — disable audience verification
    claims = jwt.decode(
        token, public_key, algorithms=['RS256'],
        options={'verify_aud': False},
        issuer=f'https://cognito-idp.{REGION}.amazonaws.com/{POOL_ID}'
    )
    if claims.get('client_id') != CLIENT_ID:
        raise ValueError('Invalid client_id')
    if claims.get('token_use') != 'access':
        raise ValueError('Invalid token_use')
    return claims

def lambda_handler(event, context):
    token = event.get('authorizationToken', '').replace('Bearer ', '')
    claims = validate_token(token)

    username = claims.get('username', claims.get('sub'))
    # Group membership is in cognito:groups (not groups)
    groups   = claims.get('cognito:groups', [])

    if not any(g in ['Administrators', 'MLUsers'] for g in groups):
        return generate_policy(username, 'Deny', event['methodArn'])

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

Data Protection and Compliance

🔒 Encryption in Transit
  • TLS 1.2 minimum for all HTTPS connections (CloudFront Security Policy: TLSv1.2_2021)
  • TLS 1.3 negotiated where clients support it
  • Certificate management via AWS Certificate Manager
  • API Gateway SSL termination
  • VPC endpoint encryption for internal traffic
🗄 Encryption at Rest
  • S3 buckets encrypted with AES-256 or SSE-KMS
  • CloudWatch Logs encrypted with KMS CMK
  • Lambda environment variables encrypted at rest
  • AWS KMS with customer-managed keys
  • Automated key rotation policies
🚫
Production API Gateway — Data Tracing Never enable dataTraceEnabled: true on API Gateway stages in production. This setting logs the full request and response payloads (including ML inputs/outputs) to CloudWatch, creating a significant security and compliance risk. Use structured access logging with accessLogDestination instead.

CORS Configuration

In production, always specify explicit allowed origins rather than using the wildcard *. The origins should correspond to your actual Amplify hosting domain and any custom domains in use.

TypeScript — CDK API Gateway CORS
defaultCorsPreflightOptions: {
  // Explicitly list allowed origins — never use Cors.ALL_ORIGINS in production
  allowOrigins: [
    'https://your-app.amplifyapp.com',  // Amplify hosted domain
    'https://your-custom-domain.com'    // Custom domain (if applicable)
  ],
  allowMethods: ['GET', 'POST', 'OPTIONS'],
  allowHeaders: ['Content-Type', 'Authorization', 'X-Amz-Date', 'X-Api-Key'],
  allowCredentials: true
}
Section 6

6Implementation Guide

Setting Up AWS Amplify

AWS Amplify Hosting can be connected to your Git repository (GitHub, GitLab, Bitbucket, or AWS CodeCommit) for automated CI/CD deployments on every push to your configured branch.

Amplify CLI Initialization

Bash
# Install and configure Amplify CLI
npm install -g @aws-amplify/cli
amplify configure

# Initialize Amplify in your Angular project
cd your-angular-project
amplify init

# Add hosting (choose Amplify Console for CI/CD)
amplify add hosting
amplify publish

Amplify Build Specification

The amplify.yml build spec is for frontend only. Infrastructure (CDK/CloudFormation) deployments must run in a separate CI/CD pipeline — not inside Amplify Hosting, which lacks the broad IAM permissions CDK requires.

YAML — amplify.yml
version: 1
frontend:
  phases:
    preBuild:
      commands:
        - cd frontend
        - npm ci
    build:
      commands:
        - npm run build:prod
  artifacts:
    baseDirectory: frontend/dist/ml-app
    files:
      - '**/*'
  cache:
    paths:
      - frontend/node_modules/**/*

Configuring Cognito User Pools

TypeScript — CDK Cognito Stack
import * as cognito from 'aws-cdk-lib/aws-cognito';

this.userPool = new cognito.UserPool(this, 'MLAppUserPool', {
  selfSignUpEnabled: false,
  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
});

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

Integrating with Entra ID

TypeScript — CDK 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',
    'custom:department': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/department'
  }
});

Angular SPA Development

Environment Configuration

TypeScript — environment.ts
export const environment = {
  production: false,
  cognito: {
    userPoolId:         'us-east-1_XXXXXXXXX',
    userPoolWebClientId: 'XXXXXXXXXXXXXXXXXXXXXXXXXX',
    // domainPrefix is the Cognito hosted UI prefix — NOT the userPoolId
    domainPrefix: 'my-company-ml-app-dev',
    region:       'us-east-1',
    identityPoolId: 'us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
  },
  api: {
    baseUrl: 'https://api.example.com/dev',
    region:  'us-east-1'
  }
};

Implementing Authentication

Use AWS Amplify v6 modular imports for authentication. The legacy import { Auth } from 'aws-amplify' pattern from Amplify v5 is no longer supported in v6 — use named function imports from aws-amplify/auth instead.

TypeScript — Amplify v6 Configuration (app.module.ts)
import { Amplify } from 'aws-amplify';
import { environment } from '../environments/environment';

// Amplify v6 configuration structure
Amplify.configure({
  Auth: {
    Cognito: {
      userPoolId:       environment.cognito.userPoolId,
      userPoolClientId: environment.cognito.userPoolWebClientId,
      identityPoolId:   environment.cognito.identityPoolId,
      loginWith: {
        oauth: {
          // Use domainPrefix — NOT userPoolId — for the OAuth domain
          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'
        }
      }
    }
  }
});
TypeScript — Auth Service (Amplify v6)
// Amplify v6: modular imports from 'aws-amplify/auth'
import { signIn, signOut, getCurrentUser, fetchAuthSession, signInWithRedirect } from 'aws-amplify/auth';

@Injectable({ providedIn: 'root' })
export class AuthService {
  private currentUserSubject = new BehaviorSubject<User | null>(null);

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

  async signInWithSAML(): Promise<void> {
    await signInWithRedirect({ provider: { custom: 'EntraID' } });
  }

  async getAccessToken(): Promise<string> {
    const session = await fetchAuthSession();
    return session.tokens?.accessToken?.toString() ?? '';
  }

  async signOut(): Promise<void> {
    await signOut();
    this.currentUserSubject.next(null);
  }
}

API Gateway Configuration

TypeScript — CDK API Gateway Stack
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as logs from 'aws-cdk-lib/aws-logs';

this.api = new apigateway.RestApi(this, 'MLAppAPI', {
  restApiName: 'ML App API',
  endpointConfiguration: { types: [apigateway.EndpointType.REGIONAL] },
  defaultCorsPreflightOptions: {
    allowOrigins: ['https://your-app.amplifyapp.com'],
    allowMethods: ['GET', 'POST', 'OPTIONS'],
    allowHeaders: ['Content-Type', 'Authorization', 'X-Amz-Date'],
    allowCredentials: true
  },
  deployOptions: {
    stageName: 'prod',
    throttlingRateLimit:  1000,
    throttlingBurstLimit: 2000,
    loggingLevel: apigateway.MethodLoggingLevel.ERROR,
    dataTraceEnabled: false,  // Never enable in production
    metricsEnabled: true,
    accessLogDestination: new apigateway.LogGroupLogDestination(
      new logs.LogGroup(this, 'ApiAccessLogs', { retention: logs.RetentionDays.THIRTY_DAYS })
    ),
    accessLogFormat: apigateway.AccessLogFormat.jsonWithStandardFields()
  }
});

Lambda Authorizer Implementation

Lambda Memory Configuration Lambda supports memory from 128 MB up to 10,240 MB (10 GB). For ML workloads, 1,024–4,096 MB is typical. Memory allocation also scales CPU proportionally.

See the complete Python Lambda Authorizer implementation in Section 5 — JWT Token Security Implementation.

ML Services Integration

See the complete Bedrock Converse API implementation in Section 4 — Bedrock Integration Flow. For SageMaker integration, invoke real-time endpoints via the AWS SDK using the endpoint name from your deployed SageMaker model.

Python — SageMaker Endpoint Invocation
import boto3, json

sagemaker_runtime = boto3.client('sagemaker-runtime', region_name=os.environ['AWS_REGION'])

def invoke_sagemaker_endpoint(endpoint_name, payload):
    response = sagemaker_runtime.invoke_endpoint(
        EndpointName=endpoint_name,
        ContentType='application/json',
        Accept='application/json',
        Body=json.dumps(payload)
    )
    result = json.loads(response['Body'].read().decode())
    return result
Back Knowledge Base stephen-ade.github.io Part 2 AWS Amplify ML Services Integration Guide CDK stacks · Lambda code · CI/CD · Deployment