diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/.env.example b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/.env.example new file mode 100644 index 00000000000..48e34d176ed --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/.env.example @@ -0,0 +1,63 @@ +FRONTEND_URL=http://localhost:8001 + +# Amazon Cognito configuration: replace these example values with your actual AWS configuration +AWS_REGION=us-east-1 +AWS_ACCOUNT_ID=123456789012 + +# Amazon Cognito identity pools configuration: replace with your actual identity pool ID from the Amazon Cognito console +COGNITO_IDENTITY_POOL_ID=us-east-1:a1b2c3d4-5678-90ab-cdef-EXAMPLE11111 + +# IAM role ARN for authenticated users: it grants permissions to users who have signed in +AUTHENTICATED_ROLE_ARN=arn:aws:iam::123456789012:role/Cognito_IdentityPoolAuth_Role +# IAM role ARN for unauthenticated users: it grants limited permissions to anonymous users +UNAUTHENTICATED_ROLE_ARN=arn:aws:iam::123456789012:role/Cognito_IdentityPoolUnauth_Role + +# Amazon Cognito user pool configuration +COGNITO_USER_POOL_ID=us-east-1_EXAMPLE123 +COGNITO_APP_CLIENT_ID=1234567890abcdefEXAMPLE +COGNITO_APP_CLIENT_SECRET=1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0tEXAMPLE +COGNITO_DOMAIN=example-domain + +# Social identity providers: configure the social identity providers you want to use +# 1. Google OAuth 2.0 configuration: obtain these values from the Google Cloud Console +GOOGLE_CLIENT_ID=123456789012-abcdefghijklmnopqrstuvwxyzEXAMPLE.apps.googleusercontent.com +GOOGLE_CLIENT_SECRET=GOCSPX-AbCdEfGhIjKlMnOpQrStUvWxYzEXAMPLE + +# 2. Facebook login configuration: obtain these values from Facebook for developers +FACEBOOK_APP_ID=1234567890123456 +FACEBOOK_APP_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4oEXAMPLE + +# 3. Login with Amazon configuration: obtain these values from the Amazon Developer Portal +AMAZON_CLIENT_ID=amzn1.application-oa2-client.a1b2c3d4e5f6g7h8i9j0k1l2m3n4oEXAMPLE +AMAZON_CLIENT_SECRET=amzn1.oa2-cs.v1.a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5zEXAMPLE + +# Enterprise identity providers: configure enterprise identity providers for corporate authentication +# 1. OpenID Connect (OIDC) provider configuration: replace with your OIDC provider's configuration + +# For Okta, these would typically be: +# OIDC_AUTHORIZATION_ENDPOINT=https://your-domain.okta.com/oauth2/default/v1/authorize +# OIDC_TOKEN_ENDPOINT=https://your-domain.okta.com/oauth2/default/v1/token + +# # For Auth0, these would typtically be: +# OIDC_AUTHORIZATION_ENDPOINT=https://your-domain.us.auth0.com/authorize +# OIDC_TOKEN_ENDPOINT=https://your-domain.us.auth0.com/oauth/token + +OIDC_CLIENT_ID=oidc_client_1234567890abcdefEXAMPLE +OIDC_CLIENT_SECRET=oidc_secret_a1b2c3d4e5f6g7h8i9j0k1l2m3n4oEXAMPLE +OIDC_AUTHORIZATION_ENDPOINT=https://your-oidc-provider.com/oauth2/authorize +OIDC_TOKEN_ENDPOINT=https://your-oidc-provider.com/oauth2/token +OIDC_ISSUER=https://your-oidc-provider.com + +# 2. SAML 2.0 provider configuration: replace with your SAML provider's configuration +# For Okta SAML, these would typically be: +# OKTA_DOMAIN=your-domain.okta.com +# OKTA_APP_ID=exkABCDEF123456789 +SAML_SSO_URL=https://example.com/saml/sso +SAML_ENTITY_ID=urn:amazon:cognito:sp:us-east-1:a1b2c3d4-5678-90ab-cdef-EXAMPLE11111 +SAML_PROVIDER=ExampleSAMLProvider +SAML_PROVIDER_ARN=arn:aws:iam::123456789012:saml-provider/ExampleSAMLProvider + +# Custom developer provider configuration: replace with your custom developer provider name +DEVELOPER_PROVIDER_NAME=ExampleDeveloperProvider +AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE +AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/README.md b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/README.md new file mode 100644 index 00000000000..602a2749105 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/README.md @@ -0,0 +1,278 @@ +# Amazon Cognito identity pools authentication flows demo + +This interactive web application demonstrates how to use Amazon Cognito identity pools with multiple identity providers. The demo shows both enhanced flow and basic flow authentication patterns, providing developers with hands-on examples of real-world integration. + +## About this demo + +Amazon Cognito identity pools enable you to create unique identities for your users and federate them with identity providers. This demo focuses on: + +* Demonstrating both enhanced and basic authentication flows in identity pools with detailed API call breakdowns +* Implementing guest (unauthenticated) access to provide limited AWS service access without requiring sign-in +* Integrating supported identity providers for different use cases: + * Social identity providers (Facebook, Amazon, and Google) for consumer access + * Enterprise identity providers (through OIDC or SAML) for corporate users + * Amazon Cognito user pools for direct user management +* Showing how to exchange identity provider tokens for temporary AWS credentials +* Demonstrating how to use these credentials to access AWS services securely + +**Note:** This demo is designed for educational purposes to help you understand identity pools and choose the right authentication approach for your applications. + +## What's included + +This demo includes the following features: + +### Authentication flows +* **Enhanced flow** – The recommended authentication flow that combines `GetId` and `GetCredentialsForIdentity` operations into a streamlined process +* **Basic flow** – The traditional authentication flow with separate `GetId`, `GetOpenIdToken`, and `AssumeRoleWithWebIdentity` operations +* **Interactive comparison** – Side-by-side demonstration of both flows with detailed API call visualization + +### Supported identity providers + +#### Social identity providers +* **Google** – OAuth 2.0 implementation with Google Sign-In +* **Facebook** – Facebook Login integration +* **Login with Amazon** – Amazon OAuth 2.0 implementation + +#### Enterprise identity providers +* **OpenID Connect (OIDC)** – Generic OIDC provider support +* **SAML 2.0** – Enterprise SAML integration with example Okta configuration + +#### Amazon Cognito services +* **Amazon Cognito user pools** – Username and password authentication with hosted UI + +#### Custom authentication +* **Developer provider** – Custom authentication system demonstration +* **Unauthenticated access** – Guest credentials for anonymous users + +### Key features +* **Modal dialogs** – Educational content explaining flow differences and provider limitations +* **Flow decision guidance** – Interactive help for choosing the right authentication approach +* **Real-time results** – Live AWS credentials with proper expiration handling +* **Security best practices** – Server-side token exchange and secure credential handling +* **Comprehensive error handling** – User-friendly error messages and troubleshooting guidance + + +## Prerequisites + +Before you begin, ensure you have the following: + +* **An AWS account** with access to Amazon Cognito. If you do not have an AWS account, see [Getting started with AWS](https://aws.amazon.com/getting-started/) to sign up for an AWS account and create a user with administrative access +* **Python 3.8 or later** installed on your development machine +* **Git** installed for cloning the repository +* **AWS credentials configured** with appropriate permissions for making authenticated requests to AWS services. +* **Developer accounts** for the identity providers you want to use (optional - you can start with one provider) + +For detailed instructions on implementing AWS credentials and identity pool federation in your specific SDK, see the [Getting credentials documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/getting-credentials.html). + +## Quick Start + +### Step 1. environment setup + +1. Clone the repository and navigate to the web directory: + ```bash + git clone + cd identity_pools_example_demo/web + ``` + +2. Install the required dependencies: + ```bash + pip install -r requirements.txt + ``` + +3. Create a `.env` file based on the `.env.example` template: + ```bash + cp .env.example .env + ``` + +### 2. Configure your environment + +Update the `.env` file with your AWS region, Identity Pool ID, and social provider credentials + +### Step 3: Run the application + +1. Start the backend server: + ```bash + python backend/oauth_server.py + ``` + + You should see output similar to: + ``` + Starting OAuth server on port 8006... + Server is running at http://localhost:8006 + ``` + +2. In a new terminal, navigate to the frontend directory and start the frontend server: + ```bash + cd frontend + python -m http.server 8001 + ``` + + You should see output similar to: + ``` + Serving HTTP on 0.0.0.0 port 8001 (http://0.0.0.0:8001/) ... + ``` + +3. Open your web browser and navigate to `http://localhost:8001` + +If successful, you should see the demo application interface with options to explore different authentication flows. + +## Identity pool setup + +### Create identity pool + +1. Open [Amazon Cognito Console](https://console.aws.amazon.com/cognito/) +2. Choose **Identity pools** → **Create identity pool** +3. Configure: + * **User access**: Enable both **Authenticated** and **Guest access** + * **Identity sources**: Select providers you plan to use +4. Create IAM roles: + * **Authenticated role**: `Cognito_IdentityPoolsDemo_Auth_Role` + * **Guest role**: `Cognito_IdentityPoolsDemo_Unauth_Role` +5. **Important**: Under **Basic (classic) authentication**, select **Activate basic flow** +6. Note your **Identity Pool ID** and **Region** + +### Configure identity providers + +Add your configured providers to the identity pool: + +1. In your identity pool, choose **Identity providers** +2. Configure each provider with the appropriate App ID/Client ID +3. For SAML: Add your SAML provider ARN +4. For OIDC: Add your OIDC provider URL + +## Provider configuration + +### Google OAuth 2.0 + +1. [Google Cloud Console](https://console.cloud.google.com/) → **Credentials** +2. Create **OAuth client ID** (Web application) +3. Add redirect URI: `http://localhost:8006/auth/google/callback` +4. Note Client ID and Client Secret + +### Facebook login + +1. [Facebook for Developers](https://developers.facebook.com/) → Create App +2. Add **Facebook Login** product +3. Add redirect URI: `http://localhost:8006/auth/facebook/callback` +4. Note App ID and App Secret + +### SAML 2.0 (Example: Okta) + +1. Okta Admin Console → Create SAML 2.0 app +2. Single Sign On URL: `http://localhost:8006/auth/saml/callback` +3. Configure attribute statements for AWS role mapping +4. AWS IAM Console → Create SAML identity provider +5. Upload Okta metadata document + +### User pools + +1. Cognito Console → **User pools** → Create or select pool +2. **App integration** → Configure Hosted UI +3. Callback URL: `http://localhost:8001/` +4. Note User Pool ID, App Client ID, and App Client Secret + +## How it works + +### Enhanced flow (recommended) +``` +User Authentication → Provider Token → GetCredentialsForIdentity → AWS Credentials +``` + +**Benefits:** +- Single API call +- Simplified implementation +- Better performance +- Automatic role selection + +### Basic Flow (Traditional) +``` +User Authentication → Provider Token → GetId → GetOpenIdToken → AssumeRoleWithWebIdentity → AWS Credentials +``` + +**Benefits:** +- Granular control +- Custom role selection +- Token inspection capability +- Legacy compatibility + +### Flow selection guidance + +The application provides interactive guidance: + +* **Enhanced flow**: Recommended for new applications, mobile apps, and when you want simplified implementation +* **Basic flow**: Choose when you need custom role selection logic or token inspection +* **SAML limitation**: SAML providers only work with enhanced flow due to automatic role selection requirements + +## Architecture + +``` +Frontend (8001) Backend (8006) AWS Services +├── Interactive UI ├── OAuth Server ├── Cognito Identity Pools +├── Flow Demonstrations ├── Token Exchange ├── IAM Roles +├── Educational Modals ├── Security Handling └── STS (Credentials) +└── Real-time Results └── Provider Integration +``` + +## Security features + +* **Server-side token exchange** – Client secrets never exposed to browser +* **Temporary credentials** – All AWS credentials have expiration times +* **CORS protection** – Proper cross-origin request handling +* **Error sanitization** – Sensitive information filtered from error messages + +## Troubleshooting + +### Common issues + +**"Identity pool not found"** +- Verify `IDENTITY_POOL_ID` in `.env` file +- Check AWS region matches your identity pool + +**"Provider not configured"** +- Ensure provider is added to identity pool in AWS Console +- Verify App ID/Client ID matches between provider and identity pool + +**SAML "Basic flow not supported"** +- SAML only works with enhanced flow +- Modal dialog explains this limitation with documentation links + +**OAuth callback errors** +- Verify callback URLs match exactly in provider developer portals +- Check that both servers are running (ports 8001 and 8006) + +### Debug information + +* Browser console shows detailed client-side logs +* Server terminal displays backend processing information +* API visualizer shows actual AWS API calls and responses + +## What you'll learn + +* **Flow Comparison** – When to use enhanced vs basic flow +* **Provider Integration** – Real OAuth 2.0, SAML, and OIDC implementations +* **Security Patterns** – Proper credential handling and token exchange +* **AWS API Usage** – Direct interaction with Cognito and STS APIs +* **Error Handling** – Production-ready error management +* **Decision Making** – Interactive guidance for architecture choices + +## Production Considerations + +Before deploying to production: + +1. **HTTPS Configuration** – Enable SSL/TLS +2. **Environment Variables** – Use secure secret management +3. **CORS Configuration** – Restrict to your domain +4. **Error Handling** – Implement comprehensive logging +5. **Rate Limiting** – Add request throttling +6. **Monitoring** – Set up CloudWatch metrics + +## Additional Resources + +* [Amazon Cognito Identity Pools Developer Guide](https://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html) +* [Authentication Flow Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/authentication-flow.html) +* [AWS SDK for Python (Boto3)](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) +* [OAuth 2.0 Security Best Practices](https://tools.ietf.org/html/draft-ietf-oauth-security-topics) + +## License + +This project is licensed under the Apache License 2.0. See the LICENSE file for details. \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/config/cognito_config.py b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/config/cognito_config.py new file mode 100644 index 00000000000..462f5c3eedb --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/config/cognito_config.py @@ -0,0 +1,31 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import os +from dotenv import load_dotenv + +# Load .env file from web directory +web_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +load_dotenv(os.path.join(web_dir, '.env'), override=True) + +COGNITO_CONFIG = { + 'REGION': os.getenv('AWS_REGION'), + 'USER_POOL_ID': os.getenv('COGNITO_USER_POOL_ID'), + 'IDENTITY_POOL_ID': os.getenv('COGNITO_IDENTITY_POOL_ID'), + 'APP_CLIENT_ID': os.getenv('COGNITO_APP_CLIENT_ID'), + 'APP_CLIENT_SECRET': os.getenv('COGNITO_APP_CLIENT_SECRET'), + 'DOMAIN': os.getenv('COGNITO_DOMAIN'), + 'ACCOUNT_ID': os.getenv('AWS_ACCOUNT_ID'), + 'SAML_PROVIDER': os.getenv('SAML_PROVIDER'), + 'OIDC_PROVIDER_URL': os.getenv('OIDC_PROVIDER_URL'), + 'DEVELOPER_PROVIDER_NAME': os.getenv('DEVELOPER_PROVIDER_NAME'), + 'AUTHENTICATED_ROLE_ARN': os.getenv('AUTHENTICATED_ROLE_ARN'), + 'UNAUTHENTICATED_ROLE_ARN': os.getenv('UNAUTHENTICATED_ROLE_ARN') +} + +# Validate required configuration +try: + from backend.config.env_config import validate_required_env_vars + validate_required_env_vars() +except ImportError: + pass \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/config/env_config.py b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/config/env_config.py new file mode 100644 index 00000000000..ec4c2a8afb4 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/config/env_config.py @@ -0,0 +1,28 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import os +from dotenv import load_dotenv + +# Load .env file from web directory (three levels up from backend/config/) +web_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +load_dotenv(os.path.join(web_dir, '.env'), override=True) + +# Validate required variables +required_vars = ['COGNITO_IDENTITY_POOL_ID', 'AWS_REGION', 'AWS_ACCOUNT_ID'] +missing = [var for var in required_vars if not os.getenv(var)] +if missing: + raise ValueError(f"Missing required environment variables: {', '.join(missing)}") + +def validate_required_env_vars(): + """Validation function for oauth_server.py startup check""" + if missing: + raise ValueError(f"Missing required environment variables: {', '.join(missing)}") + +FRONTEND_CONFIG = { + 'region': os.getenv('AWS_REGION'), + 'userPoolId': os.getenv('COGNITO_USER_POOL_ID'), + 'clientId': os.getenv('COGNITO_APP_CLIENT_ID'), + 'userPoolDomain': os.getenv('COGNITO_DOMAIN'), + 'apiEndpoint': f"{os.getenv('APP_URL', 'http://localhost:8006')}/api/authenticate" +} diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/oauth_handlers.py b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/oauth_handlers.py new file mode 100644 index 00000000000..97104920595 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/oauth_handlers.py @@ -0,0 +1,254 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import json +import urllib.parse +import requests +import boto3 +import logging +import os +import base64 +import xml.etree.ElementTree as ET +from typing import Dict, Optional + +logger = logging.getLogger(__name__) + +class OAuthFlowHandler: + def __init__(self, cognito_config: dict, oauth_providers: dict, enhanced_flow=None, basic_flow=None): + self.cognito_config = cognito_config + self.oauth_providers = oauth_providers + self.enhanced_flow = enhanced_flow + self.basic_flow = basic_flow + + def handle_token_exchange(self, provider: str, code: str, redirect_uri: str) -> dict: + """Extract the token exchange logic""" + try: + config = self.oauth_providers[provider] + token_data = { + 'grant_type': 'authorization_code', + 'client_id': config['client_id'], + 'client_secret': config['client_secret'], + 'code': code, + 'redirect_uri': redirect_uri + } + + logger.info(f"Exchanging token for {provider}") + token_response = requests.post(config['token_url'], data=token_data, timeout=(10, 30)) + + if token_response.status_code == 200: + tokens = token_response.json() + id_token = tokens.get('id_token') + access_token = tokens.get('access_token') + provider_token = id_token if id_token else access_token + + logger.info(f"Token exchange successful for {provider}") + + # Debug: Log the JWT token for OIDC + if provider.lower() == 'oidc' and provider_token: + print(f"DEBUG: OIDC JWT Token: {provider_token[:50]}...") + # Decode and print issuer + try: + import base64 + import json + parts = provider_token.split('.') + if len(parts) == 3: + payload = parts[1] + padding = len(payload) % 4 + if padding: + payload += '=' * (4 - padding) + decoded = base64.urlsafe_b64decode(payload) + jwt_payload = json.loads(decoded.decode('utf-8')) + print(f"DEBUG: JWT Issuer: {jwt_payload.get('iss', 'NOT FOUND')}") + print(f"DEBUG: JWT Audience: {jwt_payload.get('aud', 'NOT FOUND')}") + except Exception as e: + print(f"DEBUG: Failed to decode JWT: {e}") + + return { + 'success': True, + 'token': provider_token, + 'all_tokens': tokens, + 'oauth_response': tokens, # Include actual OAuth response for visualization + 'token_endpoint': config['token_url'], + 'provider_name': provider + } + else: + logger.error(f"Token exchange failed for {provider}: {token_response.status_code}") + return { + 'success': False, + 'error': f"Token exchange failed: {token_response.text}" + } + except requests.Timeout: + logger.error(f"Timeout during token exchange for {provider}") + return {'success': False, 'error': f"Request timeout for {provider}"} + except requests.RequestException as e: + logger.error(f"Network error during token exchange for {provider}: {e}") + return {'success': False, 'error': f"Network error: {str(e)}"} + except Exception as e: + logger.critical(f"Unexpected error during token exchange for {provider}: {e}") + return {'success': False, 'error': f"Token exchange error: {str(e)}"} + + def handle_enhanced_flow(self, provider: str, token: str) -> dict: + """Extract enhanced flow logic""" + try: + if not self.enhanced_flow: + return {'success': False, 'error': 'Enhanced flow not available'} + + logger.info(f"Using Enhanced Flow for {provider}") + + # Handle guest access + if provider.lower() == 'guest': + return self.enhanced_flow.enhanced_flow_guest() + + # Handle OIDC with proper issuer mapping per AWS docs + if provider.lower() == 'oidc': + # Use the issuer as the key for logins map (per AWS docs) + oidc_issuer = os.getenv('OIDC_ISSUER', 'login.provider.com') + # Use full issuer URL as provider key + cognito_provider = oidc_issuer + + # Validate token format before sending to Cognito + if not self._is_valid_jwt(token): + return self._get_oidc_setup_error() + else: + # Map other provider names to Cognito format + provider_map = { + 'google': 'Google', + 'facebook': 'Facebook', + 'amazon': 'Amazon', + 'apple': 'SignInWithApple' + } + cognito_provider = provider_map.get(provider.lower(), provider.capitalize()) + + # For OIDC, pass 'OIDC' as provider type, not the domain + flow_provider = 'OIDC' if provider.lower() == 'oidc' else provider.capitalize() + result = self.enhanced_flow.enhanced_flow_authenticated(flow_provider, token) + + # Handle OIDC-specific errors + if not result.get('success') and provider.lower() == 'oidc': + error_msg = result.get('error', '') + if 'Invalid login token' in error_msg or 'Not a valid OpenId Connect' in error_msg: + return self._get_oidc_setup_error() + + logger.info(f"Enhanced Flow result for {provider}: {result.get('success')}") + return result + + except Exception as e: + logger.critical(f"Enhanced Flow error for {provider}: {e}") + return {'success': False, 'error': f"Enhanced Flow error: {str(e)}"} + + def handle_basic_flow(self, provider: str, token: str) -> dict: + """Extract basic flow logic""" + try: + if not self.basic_flow: + return {'success': False, 'error': 'Basic flow not available'} + + logger.info(f"Using Basic Flow for {provider}") + + # Handle guest access + if provider.lower() == 'guest': + return self.basic_flow.basic_flow_guest() + + # Handle OIDC with proper issuer mapping per AWS docs + if provider.lower() == 'oidc': + # Use the issuer as the key for logins map (per AWS docs) + oidc_issuer = os.getenv('OIDC_ISSUER', 'login.provider.com') + # Use full issuer URL as provider key + cognito_provider = oidc_issuer + + # Validate token format before sending to Cognito + if not self._is_valid_jwt(token): + return self._get_oidc_setup_error() + else: + # Map other provider names to BasicFlowDemo format + provider_map = { + 'google': 'Google', + 'facebook': 'Facebook', + 'amazon': 'Amazon' + } + cognito_provider = provider_map.get(provider.lower(), provider.capitalize()) + + # For OIDC, pass 'OIDC' as provider type, not the domain + flow_provider = 'OIDC' if provider.lower() == 'oidc' else provider.capitalize() + result = self.basic_flow.basic_flow_authenticated(flow_provider, token) + + # Handle OIDC-specific errors + if not result.get('success') and provider.lower() == 'oidc': + error_msg = result.get('error', '') + if 'Invalid login token' in error_msg or 'Not a valid OpenId Connect' in error_msg: + return self._get_oidc_setup_error() + + logger.info(f"Basic Flow result for {provider}: {result.get('success')}") + return result + + except Exception as e: + logger.critical(f"Basic Flow error for {provider}: {str(e)}") + return {'success': False, 'error': f"Basic Flow error: {str(e)}"} + + def _is_valid_jwt(self, token): + """Basic JWT format validation""" + try: + parts = token.split('.') + return len(parts) == 3 # header.payload.signature + except: + return False + + def handle_saml_flow(self, saml_response: str) -> dict: + """Handle SAML authentication flow""" + try: + if not self.enhanced_flow: + return {'success': False, 'error': 'Enhanced flow not available'} + + # Use the full SAML response directly - Cognito expects the full base64 encoded response + logger.info("Using Enhanced Flow for SAML") + result = self.enhanced_flow.enhanced_flow_authenticated('SAML', saml_response) + + if not result.get('success'): + error_msg = result.get('error', '') + if 'configuration missing' in error_msg.lower(): + return self._get_saml_setup_error() + + return result + + except Exception as e: + logger.error(f"SAML Flow error: {e}") + return {'success': False, 'error': f"SAML Flow error: {str(e)}"} + + def _get_oidc_setup_error(self): + """Return user-friendly OIDC setup error message""" + return { + 'success': False, + 'error': 'OIDC provider setup required', + 'details': 'To use OIDC authentication, you need: 1) A real OIDC provider (Auth0, Okta, etc.), 2) IAM OIDC Identity Provider configured in AWS, 3) Valid environment variables (OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET)', + 'error_type': 'configuration_error' + } + + + def _extract_saml_assertion(self, saml_response: str) -> Optional[str]: + """Extract SAML assertion from SAML response""" + try: + decoded_response = base64.b64decode(saml_response).decode('utf-8') + root = ET.fromstring(decoded_response) + + namespaces = { + 'saml': 'urn:oasis:names:tc:SAML:2.0:assertion', + 'samlp': 'urn:oasis:names:tc:SAML:2.0:protocol' + } + + assertion = root.find('.//saml:Assertion', namespaces) + if assertion is not None: + assertion_str = ET.tostring(assertion, encoding='unicode') + return base64.b64encode(assertion_str.encode('utf-8')).decode('utf-8') + + return None + except Exception as e: + logger.error(f"Failed to extract SAML assertion: {e}") + return None + + def _get_saml_setup_error(self): + """Return user-friendly SAML setup error message""" + return { + 'success': False, + 'error': 'SAML provider setup required', + 'details': 'Required: 1) AWS_ACCOUNT_ID and SAML_PROVIDER in .env file, 2) SAML Identity Provider created in AWS IAM, 3) Identity pool configured with SAML provider', + 'error_type': 'configuration_error' + } diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/oauth_server.py b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/oauth_server.py new file mode 100644 index 00000000000..209b2b0266a --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/oauth_server.py @@ -0,0 +1,570 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Generic OAuth server using standard OAuth 2.0 library +""" +import json +import urllib.parse +import requests +import sys +import os +import logging +import boto3 +import base64 +from dotenv import load_dotenv + +# Focus on authentication flow progress, configuration issues, and failures +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + stream=sys.stdout, + force=True +) + +logging.getLogger('botocore').setLevel(logging.WARNING) +logging.getLogger('urllib3').setLevel(logging.WARNING) +logging.getLogger('boto3').setLevel(logging.WARNING) + +# # Ensure oauth_handlers logs are visible for authentication flow tracking +# oauth_handler_logger = logging.getLogger('backend.oauth_handlers') +# oauth_handler_logger.setLevel(logging.INFO) + +# Application logger +logger = logging.getLogger(__name__) +logger.info("OAuth server starting - authentication flows ready") + +def is_custom_domain(domain): + """Check if domain is custom (contains dots) or prefix (alphanumeric only)""" + return domain and '.' in domain + +def build_token_url(domain, region): + """Build token URL based on domain type""" + if is_custom_domain(domain): + return f'https://{domain}/oauth2/token' + else: + return f'https://{domain}.auth.{region}.amazoncognito.com/oauth2/token' + +load_dotenv() +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.parse import urlparse, parse_qs + +# Add path for enhanced flow import +backend_path = os.path.dirname(os.path.abspath(__file__)) +web_path = os.path.dirname(backend_path) +sys.path.append(web_path) + +from backend.oauth_handlers import OAuthFlowHandler + +try: + from backend.routes.auth_flows.enhanced_flow import EnhancedFlowDemo + from backend.routes.auth_flows.basic_flow import BasicFlowDemo + from backend.config.env_config import FRONTEND_CONFIG + from backend.config.cognito_config import COGNITO_CONFIG + ENHANCED_FLOW_AVAILABLE = True + BASIC_FLOW_AVAILABLE = True + logger.info("Enhanced flow and basic flow imported successfully") +except ImportError as e: + try: + from routes.auth_flows.enhanced_flow import EnhancedFlowDemo + from routes.auth_flows.basic_flow import BasicFlowDemo + from config.env_config import FRONTEND_CONFIG + from config.cognito_config import COGNITO_CONFIG + ENHANCED_FLOW_AVAILABLE = True + BASIC_FLOW_AVAILABLE = True + logger.info("Enhanced flow and basic flow imported successfully") + except ImportError as e2: + logger.error(f"Flow modules not available: {e2}") + ENHANCED_FLOW_AVAILABLE = False + BASIC_FLOW_AVAILABLE = False + FRONTEND_CONFIG = {} + COGNITO_CONFIG = {} + +# Generic OAuth configuration for different providers +OAUTH_PROVIDERS = { + 'google': { + 'client_id': os.getenv('GOOGLE_CLIENT_ID', ''), + 'client_secret': os.getenv('GOOGLE_CLIENT_SECRET', ''), + 'auth_url': 'https://accounts.google.com/o/oauth2/v2/auth', + 'token_url': 'https://oauth2.googleapis.com/token', + 'scope': 'openid email profile' + }, + 'amazon': { + 'client_id': os.getenv('AMAZON_CLIENT_ID', ''), + 'client_secret': os.getenv('AMAZON_CLIENT_SECRET', ''), + 'auth_url': 'https://www.amazon.com/ap/oa', + 'token_url': 'https://api.amazon.com/auth/o2/token', + 'scope': 'profile', + 'fallback_client_id': os.getenv('AMAZON_FALLBACK_CLIENT_ID', '') + }, + 'facebook': { + 'client_id': os.getenv('FACEBOOK_APP_ID', ''), + 'client_secret': os.getenv('FACEBOOK_APP_SECRET', ''), + 'auth_url': 'https://www.facebook.com/v18.0/dialog/oauth', + 'token_url': 'https://graph.facebook.com/v18.0/oauth/access_token', + 'scope': 'public_profile' + }, + 'saml': { + 'auth_url': os.getenv('SAML_SSO_URL', 'https://your-saml-provider.com/sso'), + 'callback_url': f"{os.getenv('APP_URL', 'http://localhost:8006')}/auth/saml/callback", + 'entity_id': os.getenv('SAML_ENTITY_ID', 'urn:amazon:cognito:sp:your-identity-pool-id') + }, + 'oidc': { + 'client_id': os.getenv('OIDC_CLIENT_ID', ''), + 'client_secret': os.getenv('OIDC_CLIENT_SECRET', ''), + 'auth_url': os.getenv('OIDC_AUTHORIZATION_ENDPOINT', ''), + 'token_url': os.getenv('OIDC_TOKEN_ENDPOINT', ''), + 'scope': 'openid email profile', + 'issuer': os.getenv('OIDC_ISSUER', '') + }, +} + +class GenericOAuthHandler(BaseHTTPRequestHandler): + def __init__(self, *args, **kwargs): + # Create flow instances only if available + enhanced_flow = None + basic_flow = None + + if ENHANCED_FLOW_AVAILABLE: + try: + enhanced_flow = EnhancedFlowDemo() + except: + pass + + if BASIC_FLOW_AVAILABLE: + try: + basic_flow = BasicFlowDemo() + except: + pass + + self.oauth_handler = OAuthFlowHandler( + COGNITO_CONFIG, + OAUTH_PROVIDERS, + enhanced_flow, + basic_flow + ) + + super().__init__(*args, **kwargs) + + def add_cors_headers(self): + frontend_url = os.getenv('FRONTEND_URL', 'http://localhost:8001') + origin = self.headers.get('Origin') + if origin == frontend_url: + self.send_header('Access-Control-Allow-Origin', origin) + + def send_error_redirect(self, error_msg): + error_param = urllib.parse.quote(error_msg) + frontend_url = os.getenv('FRONTEND_URL', 'http://localhost:8001') + self.send_response(302) + self.send_header('Location', f'{frontend_url}/?error={error_param}') + self.end_headers() + + def send_success_redirect(self, provider, result): + if result.get('success'): + flow_param = '&flow=basic' if result.get('flow_type') == 'basic_authenticated' else '&flow=enhanced' + result_param = urllib.parse.quote(json.dumps(result)) + frontend_url = os.getenv('FRONTEND_URL', 'http://localhost:8001') + redirect_url = f'{frontend_url}/?{provider}_auth=success{flow_param}&result={result_param}' + self.send_response(302) + self.send_header('Location', redirect_url) + self.end_headers() + else: + self.send_error_redirect(result.get('error', f'{provider} auth failed')) + + def do_POST(self): + print(f"POST request received: {self.path}") + + # Handle API authentication endpoint + if self.path == '/api/authenticate': + try: + content_length = int(self.headers['Content-Length']) + post_data = self.rfile.read(content_length) + request_data = json.loads(post_data.decode('utf-8')) + + provider_type = request_data.get('provider_type') + provider_token = request_data.get('provider_token') + flow_type = request_data.get('flow_type', 'enhanced') + + print(f"DEBUG: Received provider_type: '{provider_type}'") + print(f"DEBUG: Received flow_type: '{flow_type}'") + + if not provider_type or not provider_token: + self.send_response(400) + self.send_header('Content-Type', 'application/json') + self.add_cors_headers() + self.end_headers() + self.wfile.write(json.dumps({'success': False, 'error': 'Missing provider_type or provider_token'}).encode()) + return + + # Execute the appropriate flow + if flow_type == 'basic': + result = self.oauth_handler.handle_basic_flow(provider_type, provider_token) + else: + print(f"DEBUG: Calling enhanced flow with provider_type: '{provider_type}'") + result = self.oauth_handler.handle_enhanced_flow(provider_type, provider_token) + + # For API calls, we don't have OAuth response data, so use placeholder + if result.get('success') and provider_type not in ['Guest', 'Developer', 'UserPool']: + result['oauth_response'] = {'note': 'OAuth response not available for direct API calls'} + result['provider_name'] = provider_type + + # Add UserPool-specific OAuth response data for visualization + if result.get('success') and provider_type == 'UserPool': + token_url = build_token_url(COGNITO_CONFIG["DOMAIN"], COGNITO_CONFIG["REGION"]) + result['oauth_response'] = {'id_token': 'eyJraWQiOiJXWlMzZTNS...', 'token_type': 'Bearer', 'expires_in': 3600} + result['token_endpoint'] = token_url + result['provider_name'] = 'UserPool' + + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.add_cors_headers() + self.end_headers() + self.wfile.write(json.dumps(result).encode()) + return + + except Exception as e: + self.send_response(500) + self.send_header('Content-Type', 'application/json') + self.add_cors_headers() + self.end_headers() + self.wfile.write(json.dumps({'success': False, 'error': str(e)}).encode()) + return + + # Handle SAML callback POST + if self.path.startswith('/auth/saml/callback'): + try: + content_length = int(self.headers['Content-Length']) + post_data = self.rfile.read(content_length) + + # Parse form data and extract SAML response + form_data = urllib.parse.parse_qs(post_data.decode('utf-8')) + saml_response = form_data.get('SAMLResponse', [None])[0] + + if not saml_response: + self.send_response(302) + self.send_header('Location', 'http://localhost:8001/?error=saml_no_response') + self.end_headers() + return + + # Process with Enhanced Flow using handler + result = self.oauth_handler.handle_saml_flow(saml_response) + + # Store result and redirect + if result.get('success'): + result_param = urllib.parse.quote(json.dumps(result)) + flow_param = '&flow=enhanced&track_api_flow=true' # SAML only works with enhanced flow + self.send_response(302) + self.send_header('Location', f'http://localhost:8001/?saml_auth=success{flow_param}&result={result_param}') + self.end_headers() + else: + error_msg = urllib.parse.quote(result.get('error', 'SAML failed')) + self.send_response(302) + self.send_header('Location', f'http://localhost:8001/?error={error_msg}') + self.end_headers() + + except Exception as e: + error_msg = urllib.parse.quote(f"SAML error: {str(e)}") + self.send_response(302) + self.send_header('Location', f'http://localhost:8001/?error={error_msg}') + self.end_headers() + return + + # User Pool callback - handle OAuth code exchange with WAF token support + if self.path == '/auth/userpool/callback': + try: + content_length = int(self.headers['Content-Length']) + post_data = self.rfile.read(content_length) + + # Handle both JSON and form data + content_type = self.headers.get('Content-Type', '') + if 'application/json' in content_type: + request_data = json.loads(post_data.decode('utf-8')) + else: + # Parse form data + from urllib.parse import parse_qs + form_data = parse_qs(post_data.decode('utf-8')) + request_data = {k: v[0] if v else '' for k, v in form_data.items()} + + auth_code = request_data.get('code') + redirect_uri = request_data.get('redirect_uri') + waf_token = request_data.get('waf_token') # Optional WAF token + + if not auth_code: + self.send_error_response(400, 'Missing authorization code') + return + + # Try code exchange with WAF token support + result = self.exchange_code_with_sdk(auth_code, redirect_uri, waf_token) + + # If this came from CAPTCHA form, redirect back to frontend + if 'application/json' not in content_type: + if result.get('id_token'): + # Redirect to frontend with success + frontend_url = os.getenv('FRONTEND_URL', 'http://localhost:8001') + self.send_response(302) + self.send_header('Location', f'{frontend_url}/?userpool_auth=success&id_token={result["id_token"]}') + self.end_headers() + else: + # Redirect with error + error_msg = result.get('error', 'Token exchange failed') + frontend_url = os.getenv('FRONTEND_URL', 'http://localhost:8001') + self.send_response(302) + self.send_header('Location', f'{frontend_url}/?error={urllib.parse.quote(error_msg)}') + self.end_headers() + else: + self.send_json_response(result) + return + + except Exception as e: + self.send_error_response(500, str(e)) + return + + # Direct User Pool authentication with WAF token support - COMMENTED OUT + # if self.path == '/api/userpool-direct-auth': + # try: + # content_length = int(self.headers['Content-Length']) + # post_data = self.rfile.read(content_length) + # request_data = json.loads(post_data.decode('utf-8')) + # + # username = request_data.get('username') + # password = request_data.get('password') + # waf_token = request_data.get('waf_token') # Optional WAF token + # + # if not username or not password: + # self.send_error_response(400, 'Missing username or password') + # return + # + # result = self.authenticate_user_pool_sdk(username, password, waf_token) + # self.send_json_response(result) + # return + # + # except Exception as e: + # self.send_error_response(500, str(e)) + # return + + # Removed: CAPTCHA form handling - WAF handles natively + + # Handle other POST requests + self.send_response(404) + self.end_headers() + + def do_OPTIONS(self): + self.send_response(200) + self.add_cors_headers() + self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + self.end_headers() + + def do_GET(self): + # Serve frontend configuration + if self.path == '/api/config.json': + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.add_cors_headers() + self.end_headers() + self.wfile.write(json.dumps(FRONTEND_CONFIG).encode()) + return + + # Generic OAuth handler for any provider + if self.path.startswith('/auth/'): + # Remove query parameters before parsing path + clean_path = self.path.split('?')[0] + path_parts = clean_path.split('/') + + if len(path_parts) >= 3: + provider = path_parts[2] + if len(path_parts) == 3: + self.handle_oauth_login(provider) + elif len(path_parts) == 4 and path_parts[3] == 'callback': + self.handle_oauth_callback(provider) + else: + self.send_error_response(404, 'Not Found') + else: + self.send_error_response(404, 'Not Found') + else: + self.send_error_response(404, 'Not Found') + + def handle_oauth_login(self, provider): + # Convert to lowercase for case-insensitive lookup + provider_lower = provider.lower() + if provider_lower not in OAUTH_PROVIDERS: + self.send_error_response(404, f'Provider {provider} not supported') + return + + # Special handling for SAML + if provider_lower == 'saml': + self.handle_saml_login() + return + + # Use lowercase provider for config lookup + provider = provider_lower + + # Check if basic flow is requested + parsed_url = urlparse(self.path) + query_params = parse_qs(parsed_url.query) + flow_type = query_params.get('flow', ['enhanced'])[0] + + config = OAUTH_PROVIDERS[provider] + redirect_uri = f"{os.getenv('APP_URL', 'http://localhost:8006')}/auth/{provider}/callback" + + # Standard OAuth 2.0 flow for other providers + auth_url = ( + f"{config['auth_url']}?" + f"client_id={config['client_id']}&" + f"redirect_uri={urllib.parse.quote(redirect_uri)}&" + f"response_type=code&" + f"scope={urllib.parse.quote(config['scope'])}&" + f"state={flow_type}" # Pass flow type as state + ) + + self.send_response(302) + self.send_header('Location', auth_url) + self.end_headers() + + def handle_saml_login(self): + """Handle SAML login initiation""" + try: + sso_url = os.getenv('SAML_SSO_URL', 'https://example.com/saml/sso') + + if 'example.com' in sso_url: + self.send_error_redirect('SAML_SSO_URL not configured properly') + return + + self.send_response(302) + self.send_header('Location', sso_url) + self.end_headers() + + except Exception as e: + self.send_error_redirect(f'SAML login error: {str(e)}') + + def handle_oauth_callback(self, provider): + try: + # Convert to lowercase for case-insensitive lookup + provider_lower = provider.lower() + if provider_lower not in OAUTH_PROVIDERS: + self.send_error_response(404, f'Provider {provider} not supported') + return + + # Use lowercase provider for consistency + provider = provider_lower + + # Parse request + parsed_url = urlparse(self.path) + query_params = parse_qs(parsed_url.query) + code = query_params.get('code', [None])[0] + state = query_params.get('state', [None])[0] + + if not code: + return self.send_error_redirect(f'{provider}_auth_failed') + + # Use the handler + redirect_uri = f"{os.getenv('APP_URL', 'http://localhost:8006')}/auth/{provider}/callback" + token_result = self.oauth_handler.handle_token_exchange(provider, code, redirect_uri) + + if not token_result['success']: + return self.send_error_redirect(token_result['error']) + + # Handle authentication flow + if state == 'basic': + result = self.oauth_handler.handle_basic_flow(provider, token_result['token']) + else: + result = self.oauth_handler.handle_enhanced_flow(provider, token_result['token']) + + # Add OAuth response data to result for visualization + if result.get('success') and 'oauth_response' in token_result: + result['oauth_response'] = token_result['oauth_response'] + result['token_endpoint'] = token_result['token_endpoint'] + result['provider_name'] = token_result['provider_name'] + + print(f"DEBUG: Flow result: {result.get('success')}") + + # Send response + self.send_success_redirect(provider, result) + + except Exception as e: + self.send_error_redirect(f'OAuth callback error: {str(e)}') + + def send_json_response(self, data): + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.add_cors_headers() + self.end_headers() + self.wfile.write(json.dumps(data).encode()) + + def exchange_code_with_sdk(self, auth_code, redirect_uri, waf_token=None): + """Exchange OAuth code with WAF token support""" + try: + token_url = build_token_url(COGNITO_CONFIG["DOMAIN"], COGNITO_CONFIG["REGION"]) + + token_data = { + 'grant_type': 'authorization_code', + 'client_id': COGNITO_CONFIG['APP_CLIENT_ID'], + 'client_secret': COGNITO_CONFIG['APP_CLIENT_SECRET'], + 'code': auth_code, + 'redirect_uri': redirect_uri + } + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': 'AWS-SDK-Python/1.0' + } + + # Add WAF token if provided + if waf_token: + headers['x-aws-waf-token'] = waf_token + + print(f"Making request to: {token_url}") + print(f"Request data: {token_data}") + + response = requests.post(token_url, data=token_data, headers=headers, timeout=10) + + print(f"Response status: {response.status_code}") + print(f"Response text: {response.text[:500]}") + + if response.status_code == 200: + result = response.json() + # Add OAuth visualization data for User Pool + result['oauth_response'] = result.copy() + result['token_endpoint'] = token_url + result['provider_name'] = 'UserPool' + return result + else: + return { + 'error': 'TOKEN_EXCHANGE_FAILED', + 'message': f'Token exchange failed with status {response.status_code}', + 'status_code': response.status_code, + 'response_text': response.text[:200] + } + + except Exception as e: + return {'error': str(e)} + + def send_error_response(self, status_code, message): + self.send_response(status_code) + self.send_header('Content-Type', 'application/json') + self.add_cors_headers() + self.end_headers() + self.wfile.write(json.dumps({'error': message}).encode()) + +def run_server(port=8006): + try: + from backend.config.env_config import validate_required_env_vars + validate_required_env_vars() + except ImportError: + try: + from config.env_config import validate_required_env_vars + validate_required_env_vars() + except ImportError: + pass + except ValueError as e: + print(f"Configuration Error: {e}") + print("Please check your .env file and ensure all required variables are set.") + return + + server_address = ('', port) + httpd = HTTPServer(server_address, GenericOAuthHandler) + print(f'Starting OAuth server on port {port}...') + httpd.serve_forever() + +if __name__ == '__main__': + run_server() \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/routes/auth_flows/basic_flow.py b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/routes/auth_flows/basic_flow.py new file mode 100644 index 00000000000..bbe6298d5e8 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/routes/auth_flows/basic_flow.py @@ -0,0 +1,359 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +[Ongoing] Demonstrates the Basic (Classic) Flow authentication pattern for Amazon Cognito Identity Pools: +GetId → GetOpenIdToken → AssumeRoleWithWebIdentity +""" +import logging +import time +from botocore.exceptions import ClientError +import boto3 +from backend.src.identity_pool_manager import IdentityPoolManager +from backend.config.cognito_config import COGNITO_CONFIG +from backend.routes.auth_flows.enhanced_flow import EnhancedFlowDemo + +logger = logging.getLogger(__name__) + +class BasicFlowDemo: + """ + Demonstrates Basic authentication flow for Amazon Cognito Identity Pools. + + For complete usage examples, see: examples/basic_flow_examples.py + """ + + # Provider mapping constants + PROVIDER_FORMATS = { + 'Google': 'accounts.google.com', + 'Facebook': 'graph.facebook.com', + 'Amazon': 'www.amazon.com', + 'Twitter': 'api.twitter.com', + 'OIDC': None, # OIDC uses issuer domain as key + 'Developer': 'cognito-identity.amazonaws.com' + } + + def __init__(self): + """Initialize the Basic Flow demo with an identity pool manager.""" + self.identity_manager = IdentityPoolManager() # handles core operation + self.cognito_identity = boto3.client('cognito-identity', region_name=COGNITO_CONFIG['REGION']) + self.sts = boto3.client('sts', region_name=COGNITO_CONFIG['REGION']) # Boto3 client interact with AWS services + + # Session duration for credential lifetimes + self._session_duration = 3600 # 1 hour default + + # Flow state for fallback handling + self.current_flow = None + self.current_provider_token = None + self.current_provider_type = None + + def _validate_session_duration(self, duration_seconds): + """Validates the session duration is within allowed limits (15 minutes to 12 hours)""" + min_duration = 900 # 15 minutes + max_duration = 43200 # 12 hours + + if not min_duration <= duration_seconds <= max_duration: + raise ValueError( + f"Session duration must be between {min_duration} and {max_duration} seconds" + ) + def _check_role_mappings(self): + """ + Verifies that the identity pool doesn't have role mappings configured. + Basic flow cannot be used with role mappings. + """ + try: + response = self.cognito_identity.get_identity_pool_roles( + IdentityPoolId=self.identity_manager.identity_pool_id + ) + print(f"Identity pool roles response: {response}") + + # Check if the identity pool has role mappings + has_role_mappings = 'RoleMappings' in response and response['RoleMappings'] + + # Check if the identity pool has authenticated and unauthenticated roles + has_auth_roles = 'Roles' in response and 'authenticated' in response['Roles'] + has_unauth_roles = 'Roles' in response and 'unauthenticated' in response['Roles'] + + if has_role_mappings: + logger.warning("Role mappings detected. Falling back to Enhanced Flow (recommended)") + print("Role mappings detected in identity pool - Basic Flow cannot be used") + enhanced_flow = EnhancedFlowDemo() + + if self.current_flow == 'guest': + result = enhanced_flow.enhanced_flow_guest() + else: + result = enhanced_flow.enhanced_flow_authenticated( + self.current_provider_type, + self.current_provider_token + ) + + # Add educational message about Basic Flow requirements + result['fallback_reason'] = 'Role mappings detected - Basic Flow requires identity pools without role mappings' + result['basic_flow_requirements'] = [ + 'Identity pool must not have role mappings configured', + 'AWS credentials must be properly configured', + 'Identity pool must have both authenticated and unauthenticated roles assigned' + ] + return result + + # Check if the identity pool has the required roles + if not has_auth_roles or not has_unauth_roles: + logger.warning("Missing required roles. Basic Flow requires both authenticated and unauthenticated roles.") + print("Identity pool missing required roles - Basic Flow requires both authenticated and unauthenticated roles") + return { + 'success': False, + 'error': 'Basic Flow requires both authenticated and unauthenticated roles to be configured in the identity pool', + 'basic_flow_requirements': [ + 'Identity pool must have both authenticated and unauthenticated roles assigned' + ] + } + + # No role mappings and has required roles, continue with Basic Flow + print("Identity pool configuration supports Basic Flow") + return None + except ClientError as error: + logger.warning("Cannot verify role mappings, falling back to Enhanced Flow: %s", + error.response['Error']['Message']) + print(f"Error checking identity pool configuration: {error}") + enhanced_flow = EnhancedFlowDemo() + + if self.current_flow == 'guest': + result = enhanced_flow.enhanced_flow_guest() + else: + result = enhanced_flow.enhanced_flow_authenticated( + self.current_provider_type, + self.current_provider_token + ) + # Message about basic flow requirements + result['fallback_reason'] = 'AWS credential configuration issue - Basic Flow requires proper AWS setup' + result['basic_flow_requirements'] = [ + 'AWS credentials must be properly configured with cognito-identity:GetIdentityPoolRoles permission', + 'Identity pool must not have role mappings configured', + 'Identity pool must have both authenticated and unauthenticated roles assigned' + ] + return result + + + def basic_flow_guest(self): + """ + Run Basic Flow for guest (unauthenticated) access. + Falls back to Enhanced Flow if role mappings are detected. + + Returns: + dict: Contains identity ID and temporary credentials information + """ + print("Handling request for Basic Flow - Guest access") + logger.info("Starting Basic Flow - Guest (UnAuthenticated) Access") + + # Store current flow information for enhanced flow fallback + self.current_flow = 'guest' + self.current_provider_type = None + self.current_provider_token = None + try: + # Check for role mappings first + fallback_result = self._check_role_mappings() + if fallback_result: + return fallback_result + + # Step 1: GetId - Get a unique identifier for unauthenticated user + print("Step 1: Calling GetId() API...") + print(f" → Request: {{\"IdentityPoolId\": \"{self.identity_manager.identity_pool_id}\"}}") + identity_id = self.identity_manager.get_id() + print(f" → Response: Identity ID: {identity_id}") + logger.info("Got Identity ID: %s", identity_id) + + # Step 2: GetOpenIdToken - Exchange identity ID for OpenID token + print("Step 2: Calling GetOpenIdToken() API...") + print(f" → Request: {{\"IdentityId\": \"{identity_id}\"}}") + open_id_token = self.identity_manager.get_open_id_token(identity_id) + print(f" → Response: OpenID token obtained (length: {len(open_id_token)} chars)") + logger.info("Got OpenID Token") + + # Validate duration + self._validate_session_duration(duration_seconds=3600) + + # Step 3: AssumeRoleWithWebIdentity - Exchange token for AWS credentials + print("Step 3: Calling AssumeRoleWithWebIdentity() API...") + print(f" → Request: {{\"RoleArn\": \"{self.identity_manager.unauthenticated_role_arn}\", \"WebIdentityToken\": \"[JWT_TOKEN]\"}}") + credentials = self.identity_manager.assume_role_with_web_identity( + role_arn = self.identity_manager.unauthenticated_role_arn, + role_session_name = f"basic-guest-{int(time.time())}", + web_identity_token = open_id_token, + duration_seconds=3600 # 1 hour (Session duration in seconds (15 - 720 minutes)) + ) + print(f" → Response: AWS credentials obtained (AccessKeyId: {credentials['AccessKeyId'][:8]}..., expires in 1 hour)") + print("✓ Basic Flow completed successfully") + + return { + 'flow_type': 'basic_guest', + 'provider': 'Guest', + 'identity_id': identity_id, + 'open_id_token': open_id_token, + 'credentials': { + 'AccessKeyId': credentials['AccessKeyId'], + 'SecretAccessKey': credentials['SecretAccessKey'], + 'SessionToken': credentials['SessionToken'], + 'Expiration': credentials['Expiration'].isoformat() + }, + 'success': True + } + except ClientError as error: + print(f"✗ Basic Flow failed: {error.response['Error']['Message']}") + logger.error( + "Guest flow failed: %s", + error.response['Error']['Message'] + ) + return { + 'success': False, + 'error': error.response['Error'] + } + + def basic_flow_authenticated(self, provider_type, provider_token): + """ + Run Basic Flow for authenticated access with various providers. + + Args: + provider_type: Type of provider('Google', 'Facebook', 'UserPool') + provider_token: Authentication token from the provider + + Returns: + dict: Results containing identity ID, provider information, and temporary credentials + """ + + # Store current flow information for enhanced flow fallback + self.current_flow = 'authenticated' + self.current_provider_type = provider_type + self.current_provider_token = provider_token + + try: + # Check for role mappings first + fallback_result = self._check_role_mappings() + if fallback_result: + return fallback_result + + # Basic token validation + if not provider_token: + raise ValueError(f"Provider token is required for {provider_type}") + + # Get provider name from constants or dynamic mapping + if provider_type == 'Developer': + provider_name = COGNITO_CONFIG.get('DEVELOPER_PROVIDER_NAME') + elif provider_type in self.PROVIDER_FORMATS: + provider_name = self.PROVIDER_FORMATS[provider_type] + # Handle OIDC provider mapping + if provider_type == 'OIDC': + import os + oidc_issuer = os.getenv('OIDC_ISSUER', '') + provider_name = oidc_issuer # Use full issuer URL as provider key + elif provider_type in ('UserPool', 'Userpool'): + provider_name = f'cognito-idp.{COGNITO_CONFIG["REGION"]}.amazonaws.com/{COGNITO_CONFIG["USER_POOL_ID"]}' + elif provider_type == 'SAML': + provider_name = f'arn:aws:iam::{COGNITO_CONFIG.get("ACCOUNT_ID")}:saml-provider/{COGNITO_CONFIG.get("SAML_PROVIDER")}' + else: + raise ValueError(f"Unsupported provider type: {provider_type}. Must be one of: {list(self.PROVIDER_FORMATS.keys()) + ['UserPool', 'SAML']}") + logger.info(f"Starting Basic Flow - %s Authentication", provider_type) + + # Create logins map based on provider token + logins = {provider_name: provider_token} + + # Special handling for Developer authentication + if provider_type == 'Developer': + # Developer authentication uses GetOpenIdTokenForDeveloperIdentity + identity_id, open_id_token = self.identity_manager.get_open_id_token_for_developer_identity(logins) + logger.info(f"Got Developer Identity ID: %s", identity_id) + logger.info(f"Got Developer OpenID Token") + else: + # Regular flow for other providers + # Step 1: GetId - Get a unique Identity ID with provider token for authenticated user + identity_id = self.identity_manager.get_id(logins) + logger.info(f"Got Identity ID: %s", identity_id) + + # Step 2: GetOpenIdToken - Exchange identity ID for OpenID token with logins + open_id_token = self.identity_manager.get_open_id_token(identity_id, logins) + logger.info(f"Got OpenID Token: %s", open_id_token) + + # Validate session duration + self._validate_session_duration(self._session_duration) + + # Step 3: AssumeRoleWithWebIdentity - Exchange token for AWS credentials + credentials = self.identity_manager.assume_role_with_web_identity( + role_arn=self.identity_manager.authenticated_role_arn, + role_session_name=f"basic-auth-{provider_type.lower()}-{int(time.time())}", + web_identity_token=open_id_token, + duration_seconds=3600 + ) + + return { + 'flow_type': 'basic_authenticated', + 'provider': provider_type, + 'identity_id': identity_id, + 'open_id_token': open_id_token, + 'credentials': { + 'AccessKeyId': credentials['AccessKeyId'], + 'SecretAccessKey': credentials['SecretAccessKey'], + 'SessionToken': credentials['SessionToken'], + 'Expiration': credentials['Expiration'].isoformat() + }, + 'provider_key': provider_name, + 'success': True + } + except (ClientError, ValueError) as error: + logger.error( + "Authentication failed; %s", + str(error) + ) + return { + 'success': False, + 'error': str(error) + } + +def run_basic_flow_demo(): + """ + Shows how to use Basic Flow authentication with Amazon Cognito Identity Pools. + """ + logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') + demo = BasicFlowDemo() + + print("Running Amazon Cognito Identity Pools Basic Flow authentication demonstration. ") + + try: + # Demo 1: Demonstrate guest (unauthenticated) access + print("Demo 1: Demonstrating guest (unauthenticated) access: ") + result = demo.basic_flow_guest() + if result['success']: + print(f"Guest Identity ID: {result['identity_id']}") + print(f"Credentials expire: {result['credentials']['Expiration']}") + else: + print(f"Guest access failed: {result['error']}") + + + # Demo 2: Social Provider Demo + print("\n Demo 2: Demonstrating authenticated access with social providers: ") + for provider in ['Google', 'Facebook', 'Amazon']: + result = demo.basic_flow_authenticated( + provider, + f'SAMPLE_{provider.upper()}_TOKEN', + ) + if result['success']: + print(f"Authenticated with {provider} successful") + print(f"Identity ID: {result['identity_id']}") + print(f"Credentials expire: {result['credentials']['Expiration']}") + else: + print(f"Authenticated with {provider} failed: {result['error']}") + + # Demo 3: User pool demo + print("\n Demo 3: Demonstrating authenticated access with Amazon Cognito User Pool: ") + result = demo.basic_flow_authenticated( + provider_type='UserPool', + provider_token='SAMPLE-ID-TOKEN' + ) + if result['success']: + print(f"Authenticated with Cognito User Pool successful") + print(f"Identity ID: {result['identity_id']}") + print(f"Credentials expire: {result['credentials']['Expiration']}") + else: + print(f"Authenticated access with Cognito User Pool failed: {result['error']}") + except Exception as error: + print(f"Demo failed: {str(error)}") + +if __name__ == "__main__": + run_basic_flow_demo() \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/routes/auth_flows/enhanced_flow.py b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/routes/auth_flows/enhanced_flow.py new file mode 100644 index 00000000000..437c635c64f --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/routes/auth_flows/enhanced_flow.py @@ -0,0 +1,376 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Demonstrates the Enhanced Authentication Flow for Amazon Cognito Identity Pools. +""" +import boto3 +import logging +from botocore.exceptions import ClientError +from backend.config.cognito_config import COGNITO_CONFIG + +from backend.src.identity_pool_manager import IdentityPoolManager + +logger = logging.getLogger(__name__) + +class EnhancedFlowDemo: + + """ + The Enhanced Flow combines the GetId and GetCredentialsForIdentity operations into a single call. This provides + a more efficient way to obtain AWS credentials. This flow is recommended over the Basic Flow as it reduces API calls and complexity. + + For complete usage examples, see: examples/enhanced_flow_examples.py + + Key features of the Enhanced Flow: + - Simplified API calls (GetId + GetCredentialsForIdentity) + - Supports unauthenticated (guest) access + - Supports authenticated access via various providers (Social, OIDC, SAML, Cognito User Pools, Developer Authenticated) + - Automatic role selection based on the identity pool configuration + """ + + # Provider mapping constants + SOCIAL_PROVIDERS = { + 'Google': 'accounts.google.com', + 'Facebook': 'graph.facebook.com', + 'Amazon': 'www.amazon.com', + } + + OIDC_DEFAULT_PROVIDER = 'samples.auth0.com' + + def __init__(self, cognito_identity_client=None): + self.identity_manager = IdentityPoolManager() + self.cognito_identity = cognito_identity_client or boto3.client( + 'cognito-identity', + region_name=COGNITO_CONFIG['REGION'] + ) + + def get_id(self, logins=None): + """ + Gets an Identity ID from Amazon Cognito. + + Args: + logins (dict, optional): Provider tokens for authenticated access + + Returns: + str: Identity ID on success + + Raises: + ClientError: If the API call fails + """ + try: + params = {'IdentityPoolId': self.identity_manager.identity_pool_id} + if logins is not None: + params['Logins'] = logins + response = self.cognito_identity.get_id(**params) + return response['IdentityId'] + except ClientError as error: + logger.error(f"Failed to get identity ID: {error}") + raise + + def get_credentials_for_identity(self, identity_id, logins=None): + """ + Gets AWS credentials for an Identity ID. + + Args: + identity_id (str): The Identity ID + logins (dict, optional): Provider tokens for authenticated access + + Returns: + dict: AWS credentials with AccessKeyId, SecretKey, SessionToken, Expiration + + Raises: + ClientError: If the API call fails + """ + try: + params = {'IdentityId': identity_id} + if logins is not None: + params['Logins'] = logins + + response = self.cognito_identity.get_credentials_for_identity(**params) + credentials = response['Credentials'] + + if 'Expiration' in credentials: + credentials['Expiration'] = credentials['Expiration'].isoformat() + return credentials + except ClientError as error: + logger.error(f"Failed to get credentials: {error}") + raise + + def enhanced_flow_guest(self): + """ + Implements guest (unauthenticated) access using Enhanced Flow. + + This method: + 1. Gets an identity ID without authentication + 2. Gets AWS credentials with unauthenticated role + 3. Returns credentials that can be used for AWS API calls + + Returns: + dict: Authentication result containing identity ID and credentials. + + Use Case: Public content access, analytics collection + """ + try: + print("Handling request for Enhanced Flow - Guest access") + logger.info("Starting Enhanced Flow - Guest Access") + + # Step 1: Get identity ID without authentication + print("Step 1: Calling GetId() API...") + print(f" → Request: {{\"IdentityPoolId\": \"{self.identity_manager.identity_pool_id}\"}}") + identity_id = self.get_id() + print(f" → Response: Identity ID: {identity_id}") + logger.info(f"Got Guest Identity ID: {identity_id}") + + # Step 2: Get AWS credentials with unauthenticated role + print("Step 2: Calling GetCredentialsForIdentity() API...") + print(f" → Request: {{\"IdentityId\": \"{identity_id}\"}}") + credentials = self.get_credentials_for_identity(identity_id) + print(f" → Response: AWS credentials obtained (AccessKeyId: {credentials['AccessKeyId'][:8]}..., expires in 1 hour)") + print("✓ Enhanced Flow completed successfully") + logger.info(f"Got credentials:{credentials is not None}") + + return { + 'success': True, + 'flow_type': 'enhanced_guest', + 'provider': 'Guest', + 'identity_id': identity_id, + 'credentials': credentials + } + except Exception as error: + print(f"✗ Enhanced Flow failed: {str(error)}") + logger.error(f"Guest access failed: {str(error)}") + return { + 'success': False, + 'error': str(error) + } + + def enhanced_flow_authenticated(self, provider_type, provider_token): + """ + Implements authenticated access using Enhanced Flow. + + Args: + provider_type (str): Identity provider (e.g., 'Google', 'UserPool', 'OIDC') + provider_token (str): Authentication token from the provider + + Returns: + dict: Authentication result with success, identity_id, credentials + """ + try: + # Handle Developer authentication separately (uses different API) + if provider_type == 'Developer': + return self.enhanced_flow_developer_authenticated(provider_token) + + # Handle OIDC providers dynamically + if provider_type == 'OIDC': + # Use the actual OIDC issuer from environment + import os + oidc_issuer = os.getenv('OIDC_ISSUER', 'https://samples.auth0.com') + # Use the issuer exactly as configured - user must match their Identity Pool format + provider_name = oidc_issuer + elif provider_type in self.SOCIAL_PROVIDERS: + provider_name = self.SOCIAL_PROVIDERS[provider_type] + elif provider_type in ('UserPool', 'Userpool'): + provider_name = f"cognito-idp.{COGNITO_CONFIG['REGION']}.amazonaws.com/{COGNITO_CONFIG['USER_POOL_ID']}" + elif provider_type == 'SAML': + account_id = COGNITO_CONFIG.get('ACCOUNT_ID') + saml_provider = COGNITO_CONFIG.get('SAML_PROVIDER') + + if not account_id or not saml_provider: + return { + 'success': False, + 'error': 'SAML configuration missing: AWS_ACCOUNT_ID and SAML_PROVIDER required in .env file' + } + + provider_name = f'arn:aws:iam::{account_id}:saml-provider/{saml_provider}' + logger.info(f"Using SAML provider ARN: {provider_name}") + else: + raise ValueError(f"Unsupported provider: {provider_type}") + + logins = {provider_name: provider_token} + + # Standard flow for all providers including SAML + identity_id = self.get_id(logins) + logger.info(f"Got identity ID: {identity_id}") + + credentials = self.get_credentials_for_identity(identity_id, logins) + + # Get the OpenID token for display (optional for SAML) + openid_token = None + if provider_type != 'SAML': + try: + token_response = self.cognito_identity.get_open_id_token( + IdentityId=identity_id, + Logins=logins + ) + openid_token = token_response.get('Token') + except Exception as token_error: + logger.warning(f"Could not get OpenID token: {token_error}") + openid_token = None + + return { + 'success' : True, + 'flow_type': 'enhanced_authenticated', + 'provider': provider_type, + 'identity_id': identity_id, + 'credentials': credentials, + 'openid_token': openid_token, + # Enhanced data for frontend display + 'provider_key': provider_name, + 'provider_token': provider_token, + 'account_id': COGNITO_CONFIG.get('ACCOUNT_ID', '123456789012'), + 'identity_pool_id': self.identity_manager.identity_pool_id, + 'region': COGNITO_CONFIG['REGION'] + } + except Exception as error: + logger.error(f"Enhanced flow failed for {provider_type}: {str(error)}") + return { + 'success': False, + 'error': str(error) + } + + def enhanced_flow_developer_authenticated(self, developer_user_identifier): + """ + Implements the Enhanced Flow for Developer authenticated identities. + + Args: + developer_user_identifier (str): User identifier from your authentication system + + Returns: + dict: Contains identity ID and AWS credentials + """ + try: + import os + + # Check if AWS credentials are available + if not os.getenv('AWS_ACCESS_KEY_ID') or not os.getenv('AWS_SECRET_ACCESS_KEY'): + return { + 'success': False, + 'error': 'Developer authentication requires AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in .env file' + } + + # Create Cognito client with explicit credentials + cognito_client = boto3.client( + 'cognito-identity', + region_name=COGNITO_CONFIG['REGION'], + aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'), + aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY') + ) + + # Step 1: GetOpenIdTokenForDeveloperIdentity + token_response = cognito_client.get_open_id_token_for_developer_identity( + IdentityPoolId=self.identity_manager.identity_pool_id, + Logins={ + COGNITO_CONFIG.get('DEVELOPER_PROVIDER_NAME', 'MyDeveloperProvider'): developer_user_identifier + } + ) + + identity_id = token_response['IdentityId'] + token = token_response['Token'] + + # Step 2: GetCredentialsForIdentity - Exchange the token for AWS credentials + credentials_response = cognito_client.get_credentials_for_identity( + IdentityId=identity_id, + Logins={ + 'cognito-identity.amazonaws.com': token + } + ) + + credentials = credentials_response['Credentials'] + if 'Expiration' in credentials: + credentials['Expiration'] = credentials['Expiration'].isoformat() + + return { + 'success': True, + 'flow_type': 'enhanced_developer', + 'provider': 'Developer', + 'identity_id': identity_id, + 'credentials': credentials, + 'openid_token': token, + 'provider_key': COGNITO_CONFIG.get('DEVELOPER_PROVIDER_NAME', 'MyDeveloperProvider'), + 'provider_token': developer_user_identifier + } + except Exception as error: + logger.error(f"Developer authentication failed: {error}") + return { + 'success': False, + 'error': str(error) + } + + def get_credentials_with_provider(self, logins=None): + """ + Combines getting identity ID and credentials in one call. + + Args: + logins (dict, optional): Provider tokens + + Returns: + dict: Contains identity_id and credentials + """ + try: + identity_id = self.get_id(logins) + credentials = self.get_credentials_for_identity(identity_id, logins) + return { + 'identity_id': identity_id, + 'credentials': credentials + } + except Exception as error: + logger.error(f"Failed to get credentials with provider: {str(error)}") + raise + +def run_enhanced_flow_demo(): + """ + Shows how to use Enhanced Flow authentication with Amazon Cognito Identity Pools. + """ + logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') + demo = EnhancedFlowDemo() + + print("Running Amazon Cognito Identity Pools Enhanced Flow authentication demonstration.") + + try: + # Demo 1: Guest Access + print("\nDemo 1: Guest (Unauthenticated) Access") + result = demo.enhanced_flow_guest() + if result['success']: + print(f"Guest Identity ID: {result['identity_id']}") + print(f"Credentials obtained successfully") + else: + print(f"Guest access failed: {result['error']}") + + # Demo 2: Social Provider Authentication + print("\nDemo 2: Social Provider Authentication") + for provider in demo.SOCIAL_PROVIDERS.keys(): + result = demo.enhanced_flow_authenticated( + provider_type=provider, + provider_token=f'SAMPLE_{provider.upper()}_TOKEN' + ) + if result['success']: + print(f"\n{provider} Authentication:") + print(f"Identity ID: {result['identity_id']}") + print(f"Credentials obtained successfully") + else: + print(f"\n{provider} Authentication failed: {result['error']}") + + # Demo 3: Additional Provider Types + additional_demos = [ + ('UserPool', 'SAMPLE-ID-TOKEN', 'Cognito User Pool'), + ('Developer', 'SAMPLE-DEVELOPER-USER-ID', 'Developer Authenticated') + ] + + for provider_type, token, description in additional_demos: + print(f"\nDemo: {description} Authentication") + if provider_type == 'Developer': + result = demo.enhanced_flow_developer_authenticated(token) + else: + result = demo.enhanced_flow_authenticated(provider_type, token) + + if result['success']: + print(f"Identity ID: {result['identity_id']}") + print(f"Credentials obtained successfully") + else: + print(f"Authentication failed: {result['error']}") + + except Exception as error: + print(f"Demo failed: {str(error)}") + +if __name__ == '__main__': + run_enhanced_flow_demo() diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/src/identity_pool_manager.py b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/src/identity_pool_manager.py new file mode 100644 index 00000000000..88e35a443ec --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/backend/src/identity_pool_manager.py @@ -0,0 +1,98 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import boto3 +import logging +from botocore.exceptions import ClientError +from backend.config.cognito_config import COGNITO_CONFIG + +logger = logging.getLogger(__name__) + +class IdentityPoolManager: + def __init__(self): + self.cognito_identity = boto3.client( + 'cognito-identity', + region_name=COGNITO_CONFIG['REGION'], + ) + self.identity_pool_id = COGNITO_CONFIG['IDENTITY_POOL_ID'] + + # Get role ARNs from identity pool configuration + try: + response = self.cognito_identity.get_identity_pool_roles( + IdentityPoolId=self.identity_pool_id + ) + self.authenticated_role_arn = response.get('Roles', {}).get('authenticated') + self.unauthenticated_role_arn = response.get('Roles', {}).get('unauthenticated') + + if not self.authenticated_role_arn or not self.unauthenticated_role_arn: + logger.warning("Role ARNs not found in identity pool configuration") + # Use default role ARNs from config if available + self.authenticated_role_arn = COGNITO_CONFIG.get('AUTHENTICATED_ROLE_ARN') + self.unauthenticated_role_arn = COGNITO_CONFIG.get('UNAUTHENTICATED_ROLE_ARN') + except Exception as e: + logger.warning(f"Could not get identity pool roles: {str(e)}") + # Use default role ARNs from config if available + self.authenticated_role_arn = COGNITO_CONFIG.get('AUTHENTICATED_ROLE_ARN') + self.unauthenticated_role_arn = COGNITO_CONFIG.get('UNAUTHENTICATED_ROLE_ARN') + + def get_id(self, logins=None): + try: + params = {'IdentityPoolId': self.identity_pool_id} + if logins is not None: + params['Logins'] = logins + response = self.cognito_identity.get_id(**params) + return response['IdentityId'] + except ClientError as error: + logger.error("Failed to get identity ID: %s", error) + raise + + def get_credentials_for_identity(self, identity_id, logins=None): + try: + params = {'IdentityId': identity_id} + if logins is not None: + params['Logins'] = logins + response = self.cognito_identity.get_credentials_for_identity(**params) + return response['Credentials'] + except ClientError as error: + logger.error("Failed to get credentials: %s", error) + raise + + def get_open_id_token(self, identity_id, logins=None): + try: + params = { + 'IdentityId': identity_id + } + if logins: + params['Logins'] = logins + + response = self.cognito_identity.get_open_id_token(**params) + return response['Token'] + except ClientError as error: + logger.error("Failed to get OpenID token: %s", error) + raise + + def get_open_id_token_for_developer_identity(self, logins, token_duration=3600): + try: + response = self.cognito_identity.get_open_id_token_for_developer_identity( + IdentityPoolId=self.identity_pool_id, + Logins=logins, + TokenDuration=token_duration + ) + return response['IdentityId'], response['Token'] + except ClientError as error: + logger.error("Failed to get developer token: %s", error) + raise + + def assume_role_with_web_identity(self, role_arn, role_session_name, web_identity_token, duration_seconds=3600): + try: + sts = boto3.client('sts', region_name=COGNITO_CONFIG['REGION']) + response = sts.assume_role_with_web_identity( + RoleArn=role_arn, + RoleSessionName=role_session_name, + WebIdentityToken=web_identity_token, + DurationSeconds=duration_seconds + ) + return response['Credentials'] + except ClientError as error: + logger.error("Failed to assume role: %s", error) + raise diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/api/config.json b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/api/config.json new file mode 100644 index 00000000000..19d3cbdcb21 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/api/config.json @@ -0,0 +1,7 @@ +{ + "region": "us-east-1", + "userPoolId": "us-east-1_EXAMPLE123", + "clientId": "1234567890abcdefEXAMPLE", + "userPoolDomain": "example-domain", + "apiEndpoint": "http://localhost:8006/api/authenticate" +} \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/animations.css b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/animations.css new file mode 100644 index 00000000000..70b44abf802 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/animations.css @@ -0,0 +1,96 @@ +/* Fade-in animation */ +.fade-in { + opacity: 0; + transform: translateY(30px); + transition: all 0.6s ease; +} + +.fade-in.visible { + opacity: 1; + transform: translateY(0); +} + +/* Fade-in from left */ +.fade-in-left { + opacity: 0; + transform: translateX(-30px); + transition: all 0.6s ease; +} + +.fade-in-left.visible { + opacity: 1; + transform: translateX(0); +} + +/* Fade-in from right */ +.fade-in-right { + opacity: 0; + transform: translateX(30px); + transition: all 0.6s ease; +} + +.fade-in-right.visible { + opacity: 1; + transform: translateX(0); +} + +/* Scale fade-in */ +.fade-in-scale { + opacity: 0; + transform: scale(0.9); + transition: all 0.6s ease; +} + +.fade-in-scale.visible { + opacity: 1; + transform: scale(1); +} + +/* Delayed animations */ +.fade-in-delay-1 { + transition-delay: 0.1s; +} + +.fade-in-delay-2 { + transition-delay: 0.2s; +} + +.fade-in-delay-3 { + transition-delay: 0.3s; +} + +/* Smooth transitions for dynamic content */ +.demo-step, .flow-step, .trust-card, .provider-item, .api-step { + transition: all 0.3s ease; +} + +/* Animation for side panel */ +.side-panel { + transition: transform 0.3s ease; +} + +.overlay { + transition: opacity 0.3s ease; +} + +/* Purple highlight effect for cards */ +.workload-card { + position: relative; + overflow: hidden; +} + +.workload-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, #667eea, #764ba2); + transform: scaleX(0); + transition: transform 0.3s ease; +} + +.workload-card:hover::before { + transform: scaleX(1); +} \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/auth.css b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/auth.css new file mode 100644 index 00000000000..3103dbce06e --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/auth.css @@ -0,0 +1,508 @@ +/* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Authentication Flow Components */ + +/* Demo Section Styles */ +.demo-step { + margin-bottom: var(--space-16); + padding: var(--space-10); + background: var(--bg-surface); + border-radius: var(--radius-xl); + border: 1px solid rgba(255, 255, 255, 0.9); + box-shadow: var(--shadow-card); +} + +.step-title { + color: var(--color-text-primary); + font-size: var(--font-size-3xl); + margin-bottom: var(--space-4); + font-weight: var(--font-weight-medium); + line-height: var(--leading-tight); + font-kerning: normal; + text-align: center; +} + +.step-description { + color: var(--color-accent); + font-size: var(--font-size-base); + margin-bottom: var(--space-8); + line-height: var(--leading-normal); + font-kerning: normal; + text-align: center; +} + +.trust-options { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-8); +} + +.trust-card { + padding: var(--space-8); + background: var(--bg-surface-card); + border-radius: var(--radius-lg); + border: 2px solid transparent; + cursor: pointer; + transition: var(--transition-normal); + text-align: center; +} + +.trust-card:hover { + border-color: var(--color-primary-light); + transform: translateY(-0.125rem); +} + +.trust-card.selected { + border-color: var(--color-primary-light); + background: var(--bg-surface-elevated); +} + +.trust-icon { + font-size: var(--font-size-5xl); + margin-bottom: var(--space-4); +} + +.trust-card h4 { + color: var(--color-text-primary); + font-size: var(--font-size-xl); + margin-bottom: var(--space-3); + line-height: var(--leading-tight); + font-kerning: normal; +} + +.trust-card p { + color: var(--color-accent); + font-size: var(--font-size-sm); + line-height: var(--leading-normal); + font-kerning: normal; +} + +/* Success containers */ +.auth-success-container { + background: var(--bg-success); + border: 1px solid var(--border-success); + border-radius: var(--radius-md); + padding: var(--space-6); + margin: var(--space-5) 0; +} + +.flow-success-container { + background: var(--bg-surface-elevated); + border: 1px solid var(--border-primary); + border-radius: var(--radius-md); + padding: var(--space-5); + margin: var(--space-5) 0; +} + +.credentials-display-container { + background: var(--bg-surface-elevated); + border: 1px solid var(--border-primary); + border-radius: var(--radius-md); + padding: var(--space-6); + margin: var(--space-5) 0; +} + +/* Headers and titles */ +.success-title { + color: var(--color-success); + margin-top: 0; +} + +.flow-title { + color: var(--color-primary-light); + margin-top: 0; +} + +.credentials-title { + color: var(--color-primary-light); + margin-top: 0; +} + +.section-title { + color: var(--color-primary-light); + margin: var(--space-4) 0 var(--space-2) 0; +} + +/* Credential JSON display */ +.credentials-json { + background: var(--bg-code); + padding: var(--space-5); + border-radius: var(--radius-md); + overflow-x: auto; + margin: var(--space-5) 0; + text-align: left; + font-family: var(--font-family-mono); + border: 1px solid var(--border-light); + font-size: var(--font-size-xs); +} + +.credentials-json-basic { + background: var(--bg-code); + padding: var(--space-4); + border-radius: var(--radius-sm); + overflow-x: auto; + margin: var(--space-4) 0; + text-align: left; + font-family: var(--font-family-mono); +} + +/* Info boxes */ +.next-steps-box { + background: var(--bg-surface-elevated); + border: 1px solid var(--border-secondary); + border-radius: var(--radius-sm); + padding: var(--space-4); + margin: var(--space-4) 0; + text-align: left; +} + +.info-box-blue { + background: var(--bg-surface-elevated); + border: 1px solid var(--border-secondary); + border-radius: var(--radius-md); + padding: var(--space-4); + margin: var(--space-5) 0; +} + +.warning-box { + background: var(--bg-warning); + border: 1px solid var(--border-warning); + border-radius: var(--radius-md); + padding: var(--space-5); + margin-bottom: var(--space-5); +} + +.warning-box h4 { + color: var(--color-warning); + margin-bottom: var(--space-2); + line-height: var(--leading-tight); + font-kerning: normal; +} + +.warning-box p { + color: var(--color-warning); + font-size: var(--font-size-sm); + line-height: var(--leading-normal); + font-kerning: normal; +} + +/* Error container */ +.error-container { + background: var(--bg-error); + border: 1px solid var(--border-error); + border-radius: var(--radius-md); + padding: var(--space-5); + margin: var(--space-5) 0; +} + +.error-title { + color: var(--color-error); + margin-top: 0; +} + +/* Lists */ +.info-list { + margin: var(--space-2) 0; + padding-left: var(--space-5); +} + +/* Auth Buttons */ +.btn-auth-primary { + background: var(--color-primary-light); + color: var(--color-white); + padding: var(--space-3) var(--space-5); + border: none; + border-radius: var(--radius-md); + cursor: pointer; + font-weight: var(--font-weight-semibold); +} + +.btn-auth-secondary { + background: var(--color-white); + color: var(--color-primary-light); + padding: var(--space-3) var(--space-5); + border: 2px solid var(--color-primary-light); + border-radius: var(--radius-md); + cursor: pointer; + font-weight: var(--font-weight-semibold); +} + +.btn-auth-tertiary { + background: var(--color-secondary); + color: var(--color-white); + padding: var(--space-2) var(--space-5); + border: none; + border-radius: var(--radius-md); + cursor: pointer; + margin-top: var(--space-4); +} + +.btn-auth-tertiary-light { + background: var(--bg-surface); + color: var(--color-text-primary); + padding: var(--space-3) var(--space-5); + border: 1px solid var(--border-light); + border-radius: var(--radius-md); + cursor: pointer; + font-weight: var(--font-weight-semibold); +} + +.btn-reset { + background: var(--color-secondary); + color: var(--color-white); + padding: var(--space-3) var(--space-5); + border: none; + border-radius: var(--radius-md); + cursor: pointer; + font-weight: var(--font-weight-semibold); + margin-left: var(--space-4); +} + +/* Action button containers */ +.action-buttons { + display: flex; + gap: var(--space-4); + margin-top: var(--space-6); + flex-wrap: wrap; +} + +/* API flow explanation modal styles */ +.api-flow-explanation-container { + max-width: 50rem; + margin: var(--space-5) auto; + padding: var(--space-8); + background: var(--bg-surface-elevated); + border: 1px solid var(--border-primary); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-card); +} + +.explanation-header { + text-align: center; + margin-bottom: var(--space-8); + padding-bottom: var(--space-5); + border-bottom: 2px solid var(--color-secondary); +} + +.explanation-title { + color: var(--color-primary); + font-size: var(--font-size-4xl); + font-weight: var(--font-weight-semibold); + margin-bottom: var(--space-2); + font-family: var(--font-family-primary); +} + +.explanation-subtitle { + color: var(--color-accent); + font-size: var(--font-size-lg); + margin: 0; + font-family: var(--font-family-primary); +} + +.explanation-content { + display: grid; + gap: var(--space-5); + margin-bottom: var(--space-8); +} + +.flow-info-box, .flow-details-box, .learning-objectives-box { + background: var(--bg-surface); + padding: var(--space-5); + border-radius: var(--radius-md); + border-left: 4px solid var(--color-secondary); + box-shadow: var(--shadow-sm); +} + +.info-title { + color: var(--color-primary); + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); + margin-bottom: var(--space-3); + display: flex; + align-items: center; + gap: var(--space-2); + font-family: var(--font-family-primary); +} + +.explanation-list { + margin: 0; + padding-left: var(--space-5); + color: var(--color-accent); + font-family: var(--font-family-primary); +} + +.explanation-list li { + margin-bottom: var(--space-2); + line-height: var(--leading-relaxed); +} + +.flow-type-badge { + display: inline-block; + padding: var(--space-2) var(--space-4); + border-radius: var(--radius-full); + font-weight: var(--font-weight-semibold); + font-size: var(--font-size-sm); + margin-bottom: var(--space-3); + font-family: var(--font-family-primary); +} + +.flow-type-badge.enhanced { + background: var(--color-primary-light); + color: var(--color-white); +} + +.flow-type-badge.basic { + background: var(--color-secondary); + color: var(--color-white); +} + +.flow-description { + color: var(--color-accent); + line-height: var(--leading-loose); + margin: 0; + font-family: var(--font-family-primary); +} + +.visualization-note { + background: var(--bg-surface-elevated); + border: 1px solid var(--border-secondary); + border-radius: var(--radius-md); + padding: var(--space-4); + margin-top: var(--space-5); +} + +.visualization-note p { + margin: 0; + color: var(--color-primary); + font-size: var(--font-size-sm); + font-family: var(--font-family-primary); +} + +.explanation-actions { + display: flex; + gap: var(--space-3); + justify-content: center; + flex-wrap: wrap; + padding-top: var(--space-5); + border-top: 1px solid var(--border-primary); +} + +/* Loading visualization styles */ +.loading-visualization { + text-align: center; + padding: var(--space-16) var(--space-5); + background: var(--bg-surface-elevated); + border: 1px solid var(--border-primary); + border-radius: var(--radius-xl); + margin: var(--space-5); +} + +.loading-title { + color: var(--color-primary); + font-size: var(--font-size-3xl); + margin-bottom: var(--space-3); + font-family: var(--font-family-primary); + font-weight: var(--font-weight-semibold); +} + +.loading-visualization p { + color: var(--color-accent); + font-size: var(--font-size-lg); + margin-bottom: var(--space-8); + font-family: var(--font-family-primary); +} + +.loading-spinner { + width: var(--space-10); + height: var(--space-10); + border: 4px solid var(--border-secondary); + border-top: 4px solid var(--color-secondary); + border-radius: var(--radius-full); + animation: spin 1s linear infinite; + margin: 0 auto; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Flow completion actions styles */ +.flow-completion-actions { + margin-top: var(--space-8); + padding: var(--space-6); + background: var(--bg-surface-elevated); + border: 1px solid var(--border-primary); + border-radius: var(--radius-xl); + text-align: center; + box-shadow: var(--shadow-card); +} + +.completion-header { + margin-bottom: var(--space-5); +} + +.completion-title { + color: var(--color-primary); + font-size: var(--font-size-2xl); + font-weight: var(--font-weight-semibold); + margin-bottom: var(--space-2); + font-family: var(--font-family-primary); +} + +.completion-message { + color: var(--color-accent); + font-size: var(--font-size-lg); + margin: 0; + font-family: var(--font-family-primary); + line-height: var(--leading-relaxed); +} + +.completion-buttons { + display: flex; + gap: var(--space-4); + justify-content: center; + margin-bottom: var(--space-5); + flex-wrap: wrap; +} + +.completion-note { + background: var(--bg-surface); + padding: var(--space-4); + border-radius: var(--radius-md); + border-left: 4px solid var(--color-secondary); +} + +.completion-note p { + margin: 0; + color: var(--color-accent); + font-size: var(--font-size-sm); + font-family: var(--font-family-primary); + line-height: var(--leading-relaxed); +} + +/* Responsive design for auth components */ +@media (max-width: 48rem) { + .api-flow-explanation-container { + margin: var(--space-2); + padding: var(--space-5); + } + + .explanation-actions { + flex-direction: column; + } + + .explanation-actions button { + width: 100%; + } + + .completion-buttons { + flex-direction: column; + } + + .completion-buttons button { + width: 100%; + } +} \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/components.css b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/components.css new file mode 100644 index 00000000000..fbf8adac2e4 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/components.css @@ -0,0 +1,659 @@ +/* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* API visualization components */ +.api-visualizer { + background: var(--bg-surface-card); + border-radius: var(--radius-lg); + padding: var(--space-6); + margin: var(--space-6) 0; + border: 1px solid var(--border-secondary); + box-shadow: var(--shadow-md); + max-width: 43.75rem; + margin-left: auto; + margin-right: auto; +} + +.api-visualizer h3 { + color: var(--color-text-primary); + font-size: var(--font-size-xl); + margin-bottom: var(--space-5); + text-align: center; + font-weight: var(--font-weight-semibold); +} + +.flow-diagram { + display: flex; + flex-direction: column; + gap: var(--space-4); + margin: var(--space-5) 0; +} + +.api-step { + display: flex; + align-items: flex-start; + gap: var(--space-3); +} + +.step-indicator { + width: var(--space-8); + height: var(--space-8); + border-radius: var(--radius-full); + background: var(--border-light); + display: flex; + align-items: center; + justify-content: center; + font-weight: var(--font-weight-semibold); + font-size: var(--font-size-xs); + color: var(--color-accent); + flex-shrink: 0; + transition: var(--transition-normal); + margin-top: var(--space-1); +} + +.step-content { + background: var(--color-white); + border: 1px solid var(--border-light); + border-left: 4px solid var(--border-light); + border-radius: var(--radius-md); + padding: var(--space-4) var(--space-5); + flex: 1; + min-width: 0; + word-wrap: break-word; + transition: var(--transition-normal); +} + +.api-step.current .step-indicator { + background: var(--color-primary-light); + color: var(--color-white); +} + +.api-step.current .step-content { + border-left-color: var(--color-primary-light); +} + +.api-step.in-progress .step-indicator { + background: var(--color-warning); + color: var(--color-white); + animation: pulse 1.5s infinite; +} + +.api-step.in-progress .step-content { + border-left-color: var(--color-warning); +} + +.api-step.complete .step-indicator { + background: var(--color-success); + color: var(--color-white); +} + +.api-step.complete .step-indicator::after { + content: '✓'; + font-size: var(--font-size-xs); +} + +.api-step.complete .step-content { + border-left-color: var(--color-success); +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.1); } +} + +.step-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--space-3); +} + +.api-name { + font-weight: var(--font-weight-semibold); + color: var(--color-text-primary); + font-size: var(--font-size-sm); + word-break: break-word; + overflow-wrap: break-word; +} + +.step-status { + font-size: var(--font-size-xs); + text-transform: uppercase; + font-weight: var(--font-weight-semibold); + letter-spacing: 0.5px; + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-full); + background: var(--bg-surface); + color: var(--color-accent); +} + +.step-status.current { + background: var(--bg-surface-elevated); + color: var(--color-primary-light); +} + +.step-status.in-progress { + background: var(--bg-warning); + color: var(--color-warning); +} + +.step-status.complete { + background: var(--bg-success); + color: var(--color-success); +} + +.payload-content { + font-family: var(--font-family-mono); + font-size: var(--font-size-xs); + color: var(--color-accent); + background: var(--bg-code); + padding: var(--space-3); + border-radius: var(--radius-sm); + border: 1px solid var(--border-light); + line-height: var(--leading-normal); + text-align: left; +} + +.payload-label { + font-weight: var(--font-weight-semibold); + color: var(--color-text-primary); + margin-bottom: var(--space-1); +} + +.step-payload { + margin-top: var(--space-4); +} + +.payload-section { + background: var(--bg-code); + border-radius: var(--radius-md); + padding: var(--space-4); + margin-bottom: var(--space-2); + border: 1px solid var(--border-light); +} + +.payload-title { + font-size: var(--font-size-xs); + font-weight: var(--font-weight-semibold); + color: var(--color-primary-light); + margin-bottom: var(--space-2); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.payload-viewer { + background: var(--bg-code); + border-radius: var(--radius-md); + padding: var(--space-4); + margin-top: var(--space-5); + border: 1px solid var(--border-light); + min-height: var(--space-20); +} + +.payload-placeholder { + color: var(--color-accent); + font-style: italic; + text-align: center; + padding: var(--space-5); +} + +/* Side panel styles */ +.side-panel { + position: fixed; + top: 0; + right: -25rem; + width: 25rem; + height: 100vh; + background: var(--color-white); + box-shadow: -4px 0 20px rgba(0,0,0,0.15); + transition: var(--transition-normal); + z-index: var(--z-modal); + overflow-y: auto; +} + +.side-panel.open { + right: 0; +} + +.panel-header { + padding: var(--space-5); + background: var(--color-primary); + color: var(--color-white); + display: flex; + justify-content: space-between; + align-items: center; +} + +.panel-title { + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); +} + +.close-btn { + background: none; + border: none; + color: var(--color-white); + font-size: var(--font-size-2xl); + cursor: pointer; + padding: 0; + width: var(--space-8); + height: var(--space-8); + display: flex; + align-items: center; + justify-content: center; +} + +.panel-content { + padding: var(--space-8) var(--space-5); +} + +.api-info h4 { + color: var(--color-primary-light); + margin-bottom: var(--space-4); + font-size: var(--font-size-lg); +} + +.api-info p { + color: var(--color-accent); + line-height: var(--leading-normal); + margin-bottom: var(--space-4); + font-kerning: normal; +} + +.api-info .service { + background: var(--bg-surface-elevated); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-sm); + font-size: var(--font-size-sm); + color: var(--color-primary); + font-weight: var(--font-weight-semibold); + display: inline-block; + margin-bottom: var(--space-4); +} + +.doc-link { + display: inline-block; + background: var(--color-primary); + color: var(--color-white); + padding: var(--space-3) var(--space-5); + border-radius: var(--radius-md); + text-decoration: none; + font-weight: var(--font-weight-semibold); + margin-top: var(--space-5); + transition: var(--transition-fast); +} + +.doc-link:hover { + background: var(--color-primary); +} + +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); + opacity: 0; + visibility: hidden; + transition: var(--transition-normal); + z-index: var(--z-fixed); +} + +.overlay.active { + opacity: 1; + visibility: visible; +} + +/* Flip Card Styles */ +.flip-card { + background-color: transparent; + perspective: 62.5rem; + height: auto; + min-height: 38.75rem; +} + +.flip-card-inner { + position: relative; + width: 100%; + height: 100%; + text-align: center; + transition: transform 0.6s; + transform-style: preserve-3d; + cursor: pointer; +} + +.flip-card.flipped .flip-card-inner { + transform: rotateY(180deg); +} + +.flip-card-front, .flip-card-back { + position: absolute; + width: 100%; + height: 100%; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + border-radius: var(--radius-lg); + padding: var(--space-8) var(--space-6); + text-align: left; + overflow-y: hidden; + box-sizing: border-box; +} + +.flip-card-back { + transform: rotateY(180deg); +} + +.flip-hint { + margin-top: var(--space-4); + text-align: right; + font-size: var(--font-size-xs); + color: var(--color-primary); + font-style: italic; +} + +.detailed-overview .workload-card { + min-height: 20rem; + max-height: fit-content; +} + +.detailed-overview .workload-description { + max-width: none; +} + +/* Provider Components */ +.provider-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(18.75rem, 1fr)); + gap: var(--space-5); +} + +.provider-card { + padding: var(--space-6); + background: var(--bg-surface-card); + border-radius: var(--radius-lg); + border: 1px solid var(--border-light); +} + +.provider-header { + display: flex; + align-items: center; + margin-bottom: var(--space-4); +} + +.provider-logo { + width: var(--space-8); + height: var(--space-8); + margin-right: var(--space-3); + font-size: var(--font-size-2xl); +} + +.provider-categories { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-8); + margin-top: var(--space-8); +} + +.provider-categories.guest-access { + grid-template-columns: 1fr; + max-width: none; +} + +.provider-category { + background: var(--bg-surface-card); + border-radius: var(--radius-lg); + padding: var(--space-6); + border: 1px solid var(--border-light); +} + +.category-title { + color: var(--color-text-primary); + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); + margin-bottom: var(--space-4); + display: flex; + align-items: center; + line-height: var(--leading-tight); + font-kerning: normal; +} + +.category-icon { + margin-right: var(--space-2); + font-size: var(--font-size-xl); +} + +.provider-list { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.provider-item { + display: flex; + align-items: center; + padding: var(--space-3); + background: var(--bg-surface-elevated); + border-radius: var(--radius-md); + cursor: pointer; + transition: var(--transition-fast); +} + +.provider-item:hover { + background: var(--bg-surface); +} + +.provider-item-icon { + margin-right: var(--space-3); + font-size: var(--font-size-xl); +} + +.provider-item-name { + color: var(--color-text-primary); + font-weight: var(--font-weight-medium); +} + +.info-text { + font-size: var(--font-size-sm); + cursor: pointer; + margin-left: var(--space-2); + color: var(--color-primary-light); + text-decoration: underline; + font-weight: var(--font-weight-normal); +} + +.info-text:hover { + color: var(--color-primary); +} + +.step-arrow { + text-align: center; + margin: var(--space-8) 0; +} + +.category-description { + color: var(--color-accent); + font-size: var(--font-size-sm); + margin-bottom: var(--space-4); + line-height: var(--leading-normal); + font-kerning: normal; +} + +/* Flow toggle components */ +.flow-toggle { + margin-top: var(--space-4); + padding: var(--space-4); + background: var(--bg-surface-elevated); + border-radius: var(--radius-md); +} + +.flow-options { + display: flex; + gap: var(--space-2); + margin-top: var(--space-2); +} + +.flow-btn { + padding: var(--space-2) var(--space-4); + border: 1px solid var(--color-primary-light); + background: var(--color-white); + color: var(--color-primary-light); + border-radius: var(--radius-md); + cursor: pointer; + font-size: var(--font-size-sm); + transition: var(--transition-fast); +} + +.flow-btn.active { + background: var(--color-primary-light); + color: var(--color-white); +} + +.flow-selection { + margin-bottom: var(--space-8); + padding: var(--space-5); + background: var(--bg-surface-elevated); + border-radius: var(--radius-xl); + border: 1px solid var(--border-secondary); +} + +.flow-selection h4 { + color: var(--color-text-primary); + margin-bottom: var(--space-4); + line-height: var(--leading-tight); + font-kerning: normal; +} + +.flow-selection-buttons { + display: flex; + gap: var(--space-4); +} + +.flow-selection-btn { + padding: var(--space-2) var(--space-4); + border: 1px solid var(--color-primary-light); + background: var(--color-white); + color: var(--color-primary-light); + border-radius: var(--radius-md); + cursor: pointer; + font-size: var(--font-size-sm); + transition: var(--transition-fast); +} + +.flow-selection-btn.active { + background: var(--color-primary-light); + color: var(--color-white); +} + +.flow-selection-btn:hover { + background: var(--bg-surface-elevated); +} + +.flow-selection-btn.active:hover { + background: var(--color-primary-light); +} + +.flow-tabs { + display: flex; + margin-bottom: var(--space-6); + border-bottom: 1px solid var(--border-secondary); + background: var(--bg-surface-card); + border-radius: var(--radius-md) var(--radius-md) 0 0; +} + +.tab-button { + flex: 1; + padding: var(--space-4) var(--space-6); + border: none; + background: transparent; + cursor: pointer; + font-size: var(--font-size-lg); + font-weight: var(--font-weight-semibold); + color: var(--color-accent); + border-bottom: 3px solid transparent; + transition: var(--transition-fast); + font-family: inherit; +} + +.tab-button.active { + background: var(--bg-surface-card); + border-bottom-color: var(--color-primary-light); + color: var(--color-text-primary); +} + +.tab-button:hover { + background: var(--bg-surface-elevated); + color: var(--color-text-primary); +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + +/* Flow details collapsible sections */ +.flow-details { + margin: var(--space-4) 0; + border: 1px solid var(--border-light); + border-radius: var(--radius-md); +} + +.flow-summary { + padding: var(--space-3) var(--space-4); + background: var(--bg-surface-elevated); + cursor: pointer; + font-weight: var(--font-weight-medium); + color: var(--color-primary); + border-radius: var(--radius-md); + list-style: none; + transition: var(--transition-fast); +} + +.flow-summary:hover { + background: var(--bg-surface); +} + +.flow-summary::marker { + content: none; +} + +.flow-summary::before { + content: "▶ "; + transition: transform 0.2s ease; + display: inline-block; +} + +.flow-details[open] .flow-summary::before { + transform: rotate(90deg); +} + +.flow-list { + padding: var(--space-4); + margin: 0; + list-style-position: inside; +} + +.flow-list li { + color: var(--color-accent); + line-height: 1.5; + margin-bottom: var(--space-2); +} + +.flow-list li:last-child { + margin-bottom: 0; +} +/* API hint styling */ +.flow-comparison .api-hint { + text-align: left; + margin-top: var(--space-6); + padding: var(--space-4); + background: var(--bg-surface-elevated); + border-radius: var(--radius-md); + color: var(--color-accent); + border-left: 4px solid var(--color-primary); + grid-column: 1 / -1; +} \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/style.css b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/style.css new file mode 100644 index 00000000000..f7513e58f34 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/style.css @@ -0,0 +1,554 @@ +/* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +:root { + /* Color design tokens */ + --color-primary: #43004D; + --color-primary-light: #5900B2; + --color-secondary: #8B4A9C; + --color-accent: #656871; + --color-text-primary: #333843; + --color-text-secondary: #656871; + --color-success: #28a745; + --color-warning: #855900; + --color-error: #dc3545; + --color-white: #ffffff; + + /* Background design tokens */ + --bg-primary: linear-gradient(135deg, #FAF5FF 0%, #F2E5FF 100%); + --bg-secondary: linear-gradient(135deg, #F5F7FF 0%, #E8E5FF 100%); + --bg-surface: rgba(255, 255, 255, 0.8); + --bg-surface-elevated: rgba(139, 74, 156, 0.1); + --bg-surface-card: rgba(255, 255, 255, 0.9); + --bg-surface-panel: rgba(255, 255, 255, 0.6); + --bg-success: rgba(34, 139, 34, 0.1); + --bg-warning: #FFFEF0; + --bg-error: #f8d7da; + --bg-code: #f8f9fa; + + /* Border design tokens */ + --border-primary: rgba(139, 74, 156, 0.3); + --border-secondary: rgba(115, 0, 229, 0.2); + --border-success: rgba(34, 139, 34, 0.3); + --border-warning: #FFE8BD; + --border-error: #f5c6cb; + --border-light: #EBEBF0; + --border-card: rgba(255, 255, 255, 0.8); + + /* Typography design tokens */ + --font-family-primary: 'Amazon Ember Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + --font-family-mono: 'Amazon Ember Mono', 'SF Mono', Monaco, Inconsolata, 'Roboto Mono', Consolas, 'Courier New', monospace; + + /* Font size scale (rem-based) */ + --font-size-xs: 0.75rem; + --font-size-sm: 0.875rem; + --font-size-base: 1rem; + --font-size-lg: 1.125rem; + --font-size-xl: 1.25rem; + --font-size-2xl: 1.5rem; + --font-size-3xl: 2rem; + --font-size-4xl: 2.5rem; + --font-size-5xl: 3rem; + --font-size-6xl: 4rem; + + /* Font Weight Tokens */ + --font-weight-light: 300; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + + /* Spacing scale */ + --space-0: 0; + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + --space-16: 4rem; + --space-20: 5rem; + --space-24: 6rem; + --space-32: 8rem; + + /* Border radius scale */ + --radius-none: 0; + --radius-sm: 0.25rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-2xl: 1.25rem; + --radius-full: 9999px; + + /* Shadow design tokens */ + --shadow-sm: 0 1px 2px 0 rgba(67, 0, 77, 0.05); + --shadow-md: 0 4px 6px -1px rgba(67, 0, 77, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(67, 0, 77, 0.1); + --shadow-xl: 0 20px 25px -5px rgba(67, 0, 77, 0.1); + --shadow-card: 0 8px 32px rgba(115, 0, 229, 0.15); + + --leading-none: 1; + --leading-tight: 1.15; + --leading-normal: 1.4; + --leading-relaxed: 1.5; + --leading-loose: 1.6; + + /* Z-Index scale */ + --z-base: 0; + --z-dropdown: 1000; + --z-sticky: 1020; + --z-fixed: 1030; + --z-modal: 1050; + + /* Transition tokens */ + --transition-fast: 0.2s ease; + --transition-normal: 0.3s ease; + --transition-slow: 0.5s ease; +} + +/* Font Face Declarations */ +@font-face { + font-family: 'Amazon Ember Display'; + src: url('../Amazon Ember/AmazonEmberDisplay_Rg.ttf') format('truetype'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Amazon Ember Display'; + src: url('../Amazon Ember/AmazonEmberDisplay_Md.ttf') format('truetype'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Amazon Ember Display'; + src: url('../Amazon Ember/AmazonEmberDisplay_Bd.ttf') format('truetype'); + font-weight: 600; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Amazon Ember Mono'; + src: url('../Amazon Ember/AmazonEmberMono_Rg.ttf') format('truetype'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +/* Base styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: var(--font-family-primary); + background: var(--bg-primary); + min-height: 100vh; + color: var(--color-accent); +} + +/* Header */ +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-5) var(--space-10); + background: var(--bg-surface); +} + +.menu-icon { + width: var(--space-6); + height: var(--space-6); + margin-right: var(--space-5); + background: var(--color-primary); + border-radius: var(--radius-sm); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.menu-icon::before, +.menu-icon::after { + content: ''; + width: var(--space-4); + height: 2px; + background: var(--color-white); + margin: var(--space-1) 0; +} + +.logo { + font-size: var(--font-size-xl); + color: var(--color-accent); + font-weight: var(--font-weight-normal); +} + +/* Main Layout */ +.container { + max-width: 75rem; + margin: 0 auto; + padding: var(--space-16) var(--space-10); + text-align: left; +} + +.explore-label { + color: var(--color-primary); + font-size: var(--font-size-base); + margin-bottom: var(--space-5); + font-weight: var(--font-weight-medium); + text-align: center; +} + +.main-title { + font-size: var(--font-size-6xl); + font-weight: var(--font-weight-normal); + color: var(--color-primary); + line-height: var(--leading-tight); + margin-bottom: var(--space-10); + font-kerning: normal; + letter-spacing: 0; + text-align: center; + max-width: 50rem; + margin-left: auto; + margin-right: auto; +} + +.subtitle { + font-size: var(--font-size-lg); + color: var(--color-accent); + margin-bottom: var(--space-20); + max-width: 37.5rem; + margin-left: auto; + margin-right: auto; + line-height: var(--leading-normal); + font-kerning: normal; + text-align: center; +} + +/* Workloads grid */ +.workloads { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--space-10); + margin-bottom: var(--space-24); +} + +.workload-card { + background: var(--bg-surface-panel); + border-radius: var(--radius-lg); + padding: var(--space-10) var(--space-8); + text-align: left; + border: 1px solid var(--border-card); + transition: var(--transition-fast); +} + +.workload-card:hover { + transform: translateY(-0.25rem); + background: var(--bg-surface); +} + +.workload-number { + font-size: var(--font-size-5xl); + font-weight: var(--font-weight-light); + color: var(--color-primary); + margin-bottom: var(--space-3); +} + +.workload-title { + font-size: var(--font-size-2xl); + color: var(--color-text-primary); + margin-bottom: var(--space-4); + font-weight: var(--font-weight-normal); + line-height: var(--leading-tight); + font-kerning: normal; +} + +.workload-description { + font-size: var(--font-size-base); + color: var(--color-accent); + line-height: var(--leading-normal); + font-kerning: normal; + max-width: 17.5rem; +} + +/* Network/section titles */ +.network-title { + font-size: var(--font-size-4xl); + color: var(--color-primary); + margin-bottom: var(--space-10); + line-height: var(--leading-tight); + font-kerning: normal; + text-align: center; +} + +/* API call buttons */ +.api-call { + background: var(--bg-code); + border: 1px solid var(--border-light); + border-radius: var(--radius-sm); + padding: var(--space-1) var(--space-2); + font-family: var(--font-family-mono); + font-size: var(--font-size-xs); + color: var(--color-text-primary); + font-weight: var(--font-weight-normal); + cursor: pointer; + transition: var(--transition-fast); +} + +.api-call:hover { + background: var(--color-primary); + color: var(--color-white); + transform: translateY(-0.0625rem); +} + +/* Scroll arrow */ +.scroll-arrow { + text-align: center; + margin-top: var(--space-10); + cursor: pointer; +} + +.arrow-down { + font-size: var(--font-size-3xl); + color: var(--color-primary); + animation: bounce 2s infinite; +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } + 40% { transform: translateY(-0.625rem); } + 60% { transform: translateY(-0.3125rem); } +} + +/* Auth flow container */ +.auth-flow-container { + background: var(--bg-secondary); + min-height: 100vh; + padding: var(--space-16) 0; +} + +/* Flow comparison styles */ +.flow-comparison { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-10); + margin-bottom: var(--space-12); + padding: var(--space-12) var(--space-10); + background: var(--bg-surface); + border-radius: var(--radius-xl); + border: 1px solid rgba(255, 255, 255, 0.9); + box-shadow: var(--shadow-card); +} + +.flow-step { + text-align: center; + padding: var(--space-8) var(--space-5); + transition: var(--transition-normal); +} + +.flow-step:hover { + transform: translateY(-0.3125rem); +} + +.step-number { + font-size: var(--font-size-6xl); + font-weight: var(--font-weight-light); + color: var(--color-primary); + margin-bottom: var(--space-5); + display: block; +} + +.flow-step h3 { + color: var(--color-text-primary); + margin: var(--space-4) 0; + font-size: var(--font-size-3xl); + font-weight: var(--font-weight-medium); + line-height: var(--leading-tight); + font-kerning: normal; +} + +.flow-step p { + margin: var(--space-4) 0; + color: var(--color-accent); + font-size: var(--font-size-lg); + line-height: var(--leading-normal); + font-kerning: normal; +} + +.flow-step small { + color: var(--color-primary); + font-style: italic; + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); +} + +/* Detailed overview styles */ +.detailed-overview { + background: var(--bg-surface); + padding: var(--space-16) var(--space-12); + border-radius: var(--radius-xl); + margin-bottom: var(--space-10); + border: 1px solid rgba(255, 255, 255, 0.9); + box-shadow: var(--shadow-card); +} + +.detailed-overview h3 { + margin-top: 0; + color: var(--color-text-primary); + text-align: center; + margin-bottom: var(--space-16); + font-size: var(--font-size-4xl); + font-weight: var(--font-weight-medium); + line-height: var(--leading-tight); + font-kerning: normal; +} + +.cta-section { + margin-top: var(--space-12); + padding: var(--space-8); + background: var(--bg-surface-elevated); + border-radius: var(--radius-lg); + text-align: center; + border: 2px solid var(--border-secondary); +} + +.cta-section p { + margin: 0; + color: var(--color-primary); + font-weight: var(--font-weight-semibold); + font-size: var(--font-size-xl); + line-height: var(--leading-tight); + font-kerning: normal; +} + +/* Button Styles */ +.btn-primary { + background: var(--color-secondary); + color: var(--color-white); + padding: var(--space-3) var(--space-5); + border: none; + border-radius: var(--radius-md); + cursor: pointer; + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + transition: var(--transition-fast); +} + +.btn-primary:hover { + background: var(--color-primary); +} + +.btn-secondary { + background: var(--color-white); + color: var(--color-secondary); + padding: var(--space-3) var(--space-5); + border: 2px solid var(--color-secondary); + border-radius: var(--radius-md); + cursor: pointer; + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + transition: var(--transition-fast); +} + +.btn-secondary:hover { + background: var(--color-secondary); + color: var(--color-white); +} + +.btn-tertiary { + background: var(--color-secondary); + color: var(--color-white); + padding: var(--space-3) var(--space-5); + border: none; + border-radius: var(--radius-md); + cursor: pointer; + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + transition: var(--transition-fast); +} + +.btn-tertiary:hover { + background: var(--color-primary); +} + +.left-section { + display: flex; + align-items: center; +} + +/* Error message improvements */ +.setup-instructions { + background: var(--bg-warning); + border: 1px solid var(--border-warning); + border-radius: var(--radius-md); + padding: var(--space-4); + margin: var(--space-4) 0; + font-size: var(--font-size-sm); + line-height: var(--leading-normal); +} + +.help-links { + margin-top: var(--space-3); +} + +.help-links a { + color: var(--color-primary); + text-decoration: none; + font-weight: var(--font-weight-medium); + display: inline-block; + margin: var(--space-1) 0; +} + +.help-links a:hover { + text-decoration: underline; +} + +/* Responsive Design */ +@media (max-width: 48rem) { + .container { + padding: var(--space-12) var(--space-5); + } + + .main-title { + font-size: var(--font-size-4xl); + } + + .workloads { + grid-template-columns: 1fr; + gap: var(--space-6); + } + + .workload-card { + padding: var(--space-6) var(--space-5); + } + + .flow-comparison { + grid-template-columns: 1fr; + gap: var(--space-6); + } + + .flow-step { + padding: var(--space-6) var(--space-4); + } +} + +@media (min-width: 48.0625rem) and (max-width: 64rem) { + .workloads { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 64.0625rem) { + .workloads { + grid-template-columns: repeat(3, 1fr); + } +} \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/utilities.css b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/utilities.css new file mode 100644 index 00000000000..8a128b94ff7 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/css/utilities.css @@ -0,0 +1,90 @@ +/* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Utility classes */ + +/* Spacing utilities */ +.mt-0 { margin-top: 0; } +.mt-1 { margin-top: var(--space-1); } +.mt-2 { margin-top: var(--space-2); } +.mt-3 { margin-top: var(--space-3); } +.mt-4 { margin-top: var(--space-4); } +.mt-5 { margin-top: var(--space-5); } +.mt-6 { margin-top: var(--space-6); } +.mt-8 { margin-top: var(--space-8); } + +.mb-0 { margin-bottom: 0; } +.mb-1 { margin-bottom: var(--space-1); } +.mb-2 { margin-bottom: var(--space-2); } +.mb-3 { margin-bottom: var(--space-3); } +.mb-4 { margin-bottom: var(--space-4); } +.mb-5 { margin-bottom: var(--space-5); } +.mb-6 { margin-bottom: var(--space-6); } +.mb-8 { margin-bottom: var(--space-8); } + +.p-0 { padding: 0; } +.p-1 { padding: var(--space-1); } +.p-2 { padding: var(--space-2); } +.p-3 { padding: var(--space-3); } +.p-4 { padding: var(--space-4); } +.p-5 { padding: var(--space-5); } +.p-6 { padding: var(--space-6); } +.p-8 { padding: var(--space-8); } + +/* Typography Utilities */ +.text-xs { font-size: var(--font-size-xs); } +.text-sm { font-size: var(--font-size-sm); } +.text-base { font-size: var(--font-size-base); } +.text-lg { font-size: var(--font-size-lg); } +.text-xl { font-size: var(--font-size-xl); } +.text-2xl { font-size: var(--font-size-2xl); } +.text-3xl { font-size: var(--font-size-3xl); } + +.font-light { font-weight: var(--font-weight-light); } +.font-normal { font-weight: var(--font-weight-normal); } +.font-medium { font-weight: var(--font-weight-medium); } +.font-semibold { font-weight: var(--font-weight-semibold); } + +.text-primary { color: var(--color-primary); } +.text-secondary { color: var(--color-secondary); } +.text-accent { color: var(--color-accent); } + +/* Layout utilities */ +.flex { display: flex; } +.grid { display: grid; } +.hidden { display: none; } +.block { display: block; } +.inline-block { display: inline-block; } + +.items-center { align-items: center; } +.justify-center { justify-content: center; } +.justify-between { justify-content: space-between; } + +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-right { text-align: right; } + +/* Border radius utilities */ +.rounded-none { border-radius: var(--radius-none); } +.rounded-sm { border-radius: var(--radius-sm); } +.rounded { border-radius: var(--radius-md); } +.rounded-lg { border-radius: var(--radius-lg); } +.rounded-xl { border-radius: var(--radius-xl); } +.rounded-full { border-radius: var(--radius-full); } + +/* Shadow utilities */ +.shadow-sm { box-shadow: var(--shadow-sm); } +.shadow { box-shadow: var(--shadow-md); } +.shadow-lg { box-shadow: var(--shadow-lg); } +.shadow-xl { box-shadow: var(--shadow-xl); } + +/* Transition utilities */ +.transition { transition: var(--transition-fast); } +.transition-normal { transition: var(--transition-normal); } +.transition-slow { transition: var(--transition-slow); } + +/* Legacy utility classes */ +.margin-top-0 { + margin-top: 0; +} \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/index.html b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/index.html new file mode 100644 index 00000000000..02ff1b37103 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/index.html @@ -0,0 +1,728 @@ + + + + + + + Amazon Cognito Identity Pool + + + + + + + + + + + + + + + + +
+
+ +
+
Explore
+ +

Amazon Cognito identity pools authentication flows

+ +

Explore authentication flows in identity pools and how to integrate them with supported identity providers (IdP).

+ +
+
+
01.
+

Authentication flow overview

+

Amazon Cognito identity pools support two authentication flows: a basic flow that maps guest or federated users to temporary AWS credentials, and an enhanced flow that first issues an OpenID token so you can scope roles or add session tags before requesting credentials.

+
+ +
+
02.
+

Enhanced and basic flow comparison

+

Compare basic and enhanced flows in identity pools side by side and choose the right approach for your application.

+ +
+ +
+
03.
+

Interactive demo

+

Test different authentication flows and API calls using supported identity providers to obtain temporary AWS credentials for accessing AWS services.

+
+
+ +
+
+
+
+ + +
+
+

Authentication flow overview

+

Amazon Cognito identity pools create unique identifiers for your end users that remain consistent across devices and platforms. When users authenticate with supported identity providers, identity pools exchange the provider tokens for temporary, limited-privilege AWS credentials that your application can use to access AWS resources. This credential exchange process uses one of two authentication flows: basic and enhanced flow. Each flow uses the following sequence of API calls to accomplish this exchange.

+ + +
+
+
A
+

Enhanced flow

+

+ GetId() → + GetCredentialsForIdentity() +

+ Combines identity retrieval and credential generation +
+
+
B
+

Basic flow

+

+ GetId() → + GetOpenIdToken() → + AssumeRoleWithWebIdentity() +

+ Separates identity retrieval and credential generation +
+ +

Note: Click any API call above to view its purpose, parameters, and role in the identity pool authentication process.

+
+ + +
+

Enhanced and basic flow comparison

+

This section covers the detailed API sequences, implementation approaches, and decision criteria for choosing between enhanced and basic flows in Amazon Cognito identity pools. For complete implementation details, security considerations, and developer authentication patterns, see identity pools authentication flow.

+
+ +
+
A
+

Enhanced flow

+

The enhanced flow is a two-request sequence—GetID() followed by GetCredentialsForIdentity()—that produces AWS credentials valid for one hour. In this context, client means your application code (mobile app, web front end, or server-side component) sends requests to the Amazon Cognito Identity API.

+ +

In the enhanced flow, your code never needs to call AWS Security Token Service (STS) directly. Once the identity pool validates the identity token to prove who the user is, it issues an OpenID token that uses internally to call STS AssumeRoleWithWebIdentity() on your behalf and then returns the resulting temporary credentials in the response to GetCredentialsForIdentity().

+ +
+ When to choose enhanced flow +
    +
  • Want Cognito identity pool to directly exchange AWS credentials for your users without managing STS calls manually
  • +
  • Want reduced client-side complexity: the SDK handles credentials automatically after user authentication
  • +
  • Your app's logic doesn't require signing its own GetCredentialsForIdentity or AssumeRoleWithWebIdentity requests
  • +
+
+ +
+ Benefits +
    +
  • Fewer round-trips - Only 2 API calls
  • +
  • Centralized role logic - identity pool handles RBAC/ABAC
  • +
  • Built-in security - Automatic scope-down policies
  • +
+
+
+ + +
+
B
+

Basic flow

+

The basic flow uses a three-step process: GetId() followed by GetOpenIdToken() and then AssumeRoleWithWebIdentity(). Unlike the enhanced flow, this approach gives you control over session duration (fifteen minutes to twelve hours) and role selection.

+ +

Your application handles the AWS Security Token Service (STS) interaction directly. After the identity pool validates your user's identity token, it returns an OpenID token via GetOpenIdToken(). You then use this token to call AssumeRoleWithWebIdentity() and receive temporary AWS credentials.

+
+ When to choose basic flow +
    +
  • Want full control over the STS request process
  • +
  • Need for custom credential fetching or extra security checks before granting AWS access
  • +
  • Security policy requires explicit AWS STS call rather than letting Cognito handle them
  • +
+
+ +
+ Trade-offs +
    +
  • Extra steps - 3 API calls required
  • +
  • Client-side logic - App handles role selection
  • +
  • Incompatible - Won't work with role mappings
  • +
+
+
+
+ +
+

Ready to explore how enhanced and basic flows integrate with supported identity providers in identity pool? Scroll down or choose the arrow below to explore interactive demos!

+
+
+ +
+
+
+
+
+ + +
+
+

Interactive demo

+

Test different identity pool access patterns and see how identity pools provide temporary AWS credentials for various user types.

+ + +
+

Step 1: Choose user access type info

+

Select how users will access your application resources

+ +
+
+

Authenticated access

+

Issue AWS credentials to users who sign in through trusted identity providers such as social providers (Google, Facebook), enterprise providers (SAML, OIDC), or Cognito user pools.

+
+
+

Guest access

+

Issue temporary AWS credentials to anonymous users without requiring sign-in. Guest access is designed for public resources like graphic assets, documents, or limited app functionality that encourages user registration.

+
+
+
+ + + + + + +
+
+ + + + + +
+
+
+
API reference
+ utton> +
+
+
+
+
+
div> + + + + + \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/animations.js b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/animations.js new file mode 100644 index 00000000000..2f52c3c2817 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/animations.js @@ -0,0 +1,60 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Global animation handler for the entire website +(function() { + 'use strict'; + + // Enhanced animation handler that supports all animation types + function handleGlobalAnimations() { + const animationClasses = ['.fade-in', '.fade-in-left', '.fade-in-right', '.fade-in-scale']; + + animationClasses.forEach(className => { + const elements = document.querySelectorAll(className + ':not(.visible)'); + elements.forEach(element => { + const elementTop = element.getBoundingClientRect().top; + const elementVisible = 150; + + if (elementTop < window.innerHeight - elementVisible) { + element.classList.add('visible'); + } + }); + }); + } + + // Initialize animations on page load + function initAnimations() { + // Initial animation check + setTimeout(handleGlobalAnimations, 200); + + // Add scroll listener + let ticking = false; + window.addEventListener('scroll', function() { + if (!ticking) { + requestAnimationFrame(function() { + handleGlobalAnimations(); + ticking = false; + }); + ticking = true; + } + }); + + // Add resize listener + window.addEventListener('resize', handleGlobalAnimations); + } + + // Trigger animations for dynamically loaded content + window.triggerAnimations = function() { + setTimeout(handleGlobalAnimations, 100); + }; + + // Initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initAnimations); + } else { + initAnimations(); + } + + // Make the animation handler globally available + window.handleScrollAnimation = handleGlobalAnimations; +})(); \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/app-init.js b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/app-init.js new file mode 100644 index 00000000000..bbd5fb15696 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/app-init.js @@ -0,0 +1,273 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +document.addEventListener('DOMContentLoaded', async function() { + try { + await loadConfig(); + console.log('Config loaded successfully:', CONFIG); + } catch (error) { + console.error('Failed to load config:', error); + alert('Configuration loading failed: ' + error.message); + } + + const urlParams = new URLSearchParams(window.location.search); + const authTypes = ['google', 'amazon', 'facebook', 'oidc', 'saml', 'userpool']; + + // Check for basic flow parameters + let isBasicFlow = false; + let basicAuthType = null; + + for (const type of authTypes) { + const basicAuthStatus = urlParams.get(`${type}_basic_auth`); + if (basicAuthStatus) { + isBasicFlow = true; + basicAuthType = type; + break; + } + } + + + // Check for regular auth parameters or basic auth parameters + authTypes.forEach(type => { + const authStatus = urlParams.get(`${type}_auth`); + const basicAuthStatus = urlParams.get(`${type}_basic_auth`); + + if (authStatus === 'success' || authStatus === 'error' || basicAuthStatus === 'success' || basicAuthStatus === 'error') { + // Handle SAML session-based result + if (type === 'saml' && authStatus === 'success') { + const sessionId = urlParams.get('session'); + if (sessionId) { + handleSAMLSession(sessionId, type, isBasicFlow); + return; + } + } + document.querySelectorAll('.trust-card').forEach(card => card.classList.remove('selected')); + document.querySelector('.trust-card[onclick*="authenticated"]').classList.add('selected'); + + const providerStep = document.getElementById('provider-step'); + const providerContent = document.getElementById('provider-content'); + const stepArrow = document.getElementById('step-arrow'); + + providerStep.style.display = 'block'; + stepArrow.style.display = 'block'; + + providerContent.innerHTML = ` +

Choose your authentication flow and select identity providers

+ +
+ + +
+ +
+ ${getProviderCategories('enhanced')} +
+ +
+ ${getProviderCategories('basic')} +
+ `; + + const resultParam = urlParams.get('result'); + if (resultParam) { + try { + const result = JSON.parse(decodeURIComponent(resultParam)); + const displayType = basicAuthType || type; + const successStatus = authStatus === 'success' || basicAuthStatus === 'success'; + + // Check if API flow tracking is enabled (session storage or URL parameter) + const trackApiFlowParam = urlParams.get('track_api_flow'); + const shouldShowAPIFlow = sessionStorage.getItem('track_api_flow') === 'true' || trackApiFlowParam === 'true'; + + if (shouldShowAPIFlow && successStatus) { + // Fix provider name case for user pool and Google + let correctedDisplayType = displayType === 'userpool' ? 'UserPool' : displayType; + if (correctedDisplayType === 'google') correctedDisplayType = 'Google'; + if (correctedDisplayType === 'facebook') correctedDisplayType = 'Facebook'; + if (correctedDisplayType === 'amazon') correctedDisplayType = 'Amazon'; + if (correctedDisplayType === 'saml') correctedDisplayType = 'SAML'; + if (correctedDisplayType === 'userpool') correctedDisplayType = 'UserPool'; + + // Store the result globally for API flow visualization + window.currentAuthResult = result; + showAuthSuccessWithOptions(result, correctedDisplayType); + sessionStorage.removeItem('track_api_flow'); + } else { + if (successStatus) { + showResult(`${displayType.charAt(0).toUpperCase() + displayType.slice(1)} authentication successful!`, result); + } else { + showResult(`${displayType.charAt(0).toUpperCase() + displayType.slice(1)} authentication failed!`, result); + } + } + } catch (e) { + showResult(`${type.charAt(0).toUpperCase() + type.slice(1)} authentication completed!`, null); + } + } + window.history.replaceState({}, document.title, window.location.pathname); + + setTimeout(() => { + document.getElementById('provider-step').scrollIntoView({ behavior: 'smooth', block: 'center' }); + }, 100); + } + }); + + // Check if page content contains JSON tokens (from WAF redirect) + setTimeout(() => { + const pageContent = document.body.textContent || document.body.innerText; + if (pageContent.includes('id_token') && pageContent.includes('access_token')) { + try { + const tokens = JSON.parse(pageContent.trim()); + if (tokens.id_token) { + handleTokenResponse(tokens); + return; + } + } catch (e) { + console.log('Not JSON tokens:', e); + } + } + }, 100); + + const authCode = urlParams.get('code'); + if (authCode) { + document.querySelectorAll('.trust-card').forEach(card => card.classList.remove('selected')); + document.querySelector('.trust-card[onclick*="authenticated"]').classList.add('selected'); + + const providerStep = document.getElementById('provider-step'); + const providerContent = document.getElementById('provider-content'); + const stepArrow = document.getElementById('step-arrow'); + + providerStep.style.display = 'block'; + stepArrow.style.display = 'block'; + + providerContent.innerHTML = ` +

Choose your authentication flow and select identity providers

+ +
+ + +
+ +
+ ${getProviderCategories('enhanced')} +
+ +
+ ${getProviderCategories('basic')} +
+ `; + + exchangeCodeForTokens(authCode); + window.history.replaceState({}, document.title, window.location.pathname); + + setTimeout(() => { + document.getElementById('provider-step').scrollIntoView({ behavior: 'smooth', block: 'center' }); + }, 100); + } +}); + +// Handle token response from WAF (if successful) +async function handleTokenResponse(tokens) { + if (tokens && tokens.id_token) { + // Replace page content with processing message + document.body.innerHTML = ` + + Processing... + +

Processing User Pool Authentication...

+

Please wait while we complete the authentication flow...

+ + + + `; + + // Wait for scripts to load + setTimeout(async () => { + const flowType = sessionStorage.getItem('flow_type') || 'enhanced'; + + try { + const response = await fetch('http://localhost:8006/api/authenticate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + provider_type: 'UserPool', + provider_token: tokens.id_token, + flow_type: flowType + }) + }); + + const authResult = await response.json(); + + // Redirect back to main app with result + const resultParam = encodeURIComponent(JSON.stringify(authResult)); + window.location.href = `http://localhost:8001/?userpool_success=true&result=${resultParam}`; + + } catch (error) { + window.location.href = `http://localhost:8001/?error=${encodeURIComponent(error.message)}`; + } + }, 500); + } +} + +// Handle SAML session-based authentication result +async function handleSAMLSession(sessionId, type, isBasicFlow) { + try { + const response = await fetch(`http://localhost:8006/api/saml-session/${sessionId}`); + if (response.ok) { + const result = await response.json(); + + // Set up the UI like other providers + document.querySelectorAll('.trust-card').forEach(card => card.classList.remove('selected')); + document.querySelector('.trust-card[onclick*="authenticated"]').classList.add('selected'); + + const providerStep = document.getElementById('provider-step'); + const providerContent = document.getElementById('provider-content'); + const stepArrow = document.getElementById('step-arrow'); + + providerStep.style.display = 'block'; + stepArrow.style.display = 'block'; + + providerContent.innerHTML = ` +

Choose your authentication flow and select identity providers

+ +
+ + +
+ +
+ ${getProviderCategories('enhanced')} +
+ +
+ ${getProviderCategories('basic')} +
+ `; + + // Check if API flow tracking is enabled + if (sessionStorage.getItem('track_api_flow') === 'true') { + window.currentAuthResult = result; + if (result.success) { + showAuthSuccessWithOptions(result, 'SAML'); + } else { + showResult('SAML authentication failed!', result); + } + sessionStorage.removeItem('track_api_flow'); + } else { + if (result.success) { + showResult('SAML authentication successful!', result); + } else { + showResult('SAML authentication failed!', result); + } + } + + // Clean up URL + window.history.replaceState({}, document.title, window.location.pathname); + + document.getElementById('provider-step').scrollIntoView({ behavior: 'smooth', block: 'center' }); + } else { + showResult('SAML session retrieval failed', null); + } + } catch (error) { + showResult(`SAML session error: ${error.message}`, null); + } +} \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/auth-core.js b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/auth-core.js new file mode 100644 index 00000000000..2305ce290bd --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/auth-core.js @@ -0,0 +1,396 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Configuration - Load from environment or backend +let CONFIG = { + region: '', + userPoolId: '', + clientId: '', + userPoolDomain: '', + apiEndpoint: '' +}; + +// Domain detection helper functions +function isCustomDomain(domain) { + return domain && domain.includes('.'); +} + +function buildHostedUIUrl(domain, region, clientId, redirectUri) { + const baseUrl = isCustomDomain(domain) + ? `https://${domain}` + : `https://${domain}.auth.${region}.amazoncognito.com`; + + return `${baseUrl}/login?client_id=${clientId}&response_type=code&scope=openid%20email%20profile&redirect_uri=${encodeURIComponent(redirectUri)}`; +} + +window.flowManager = null; +window.currentAuthResult = null; + +async function loadConfig() { + try { + const response = await fetch('http://localhost:8006/api/config.json'); + if (response.ok) { + CONFIG = await response.json(); + console.log('Configuration loaded from backend:', CONFIG); + return; + } else { + console.error('Config response not ok:', response.status, response.statusText); + } + } catch (error) { + console.error('Failed to load configuration:', error.message); + } + + // No fallback - force proper configuration + throw new Error('Configuration not found. Please check your .env file and restart the server.'); +} + +function getBaseUrl() { + return CONFIG.apiEndpoint.replace('/api/authenticate', ''); +} + +// Social identity providers - Generic function +function signInSocialProvider(provider, flowType = 'enhanced') { + sessionStorage.setItem('track_api_flow', 'true'); + sessionStorage.setItem('flow_type', flowType); + sessionStorage.setItem('provider', provider); + if (flowType === 'basic') { + window.location.href = `${getBaseUrl()}/auth/${provider}?flow=basic`; + } else { + window.location.href = `${getBaseUrl()}/auth/${provider}`; + } +} + +// Backward compatible wrappers +function signInGoogle() { signInSocialProvider('google', 'enhanced'); } +function signInFacebook() { signInSocialProvider('facebook', 'enhanced'); } +function signInAmazon() { signInSocialProvider('amazon', 'enhanced'); } +function signInGoogleBasic() { signInSocialProvider('google', 'basic'); } +function signInFacebookBasic() { signInSocialProvider('facebook', 'basic'); } +function signInAmazonBasic() { signInSocialProvider('amazon', 'basic'); } + +// Enterprise Providers - Generic approach following AWS requirements +function signInOIDC() { + signInSocialProvider('oidc', 'enhanced'); +} + +function signInOIDCBasic() { + signInSocialProvider('oidc', 'basic'); +} + +function signInSAML() { + window.location.href = `${getBaseUrl()}/auth/saml`; +} + +// User pool providers - Use Hosted UI as intended +function signInUserPool(flowType = 'enhanced') { + if (!CONFIG.userPoolDomain || !CONFIG.clientId) { + alert('User Pool configuration missing. Please check your .env file.'); + return; + } + sessionStorage.setItem('track_api_flow', 'true'); + sessionStorage.setItem('flow_type', flowType); + sessionStorage.setItem('provider', 'userpool'); + const redirectUri = window.location.origin + '/'; + const hostedUIUrl = buildHostedUIUrl(CONFIG.userPoolDomain, CONFIG.region, CONFIG.clientId, redirectUri); + window.location.href = hostedUIUrl; +} + + + +// Backward compatible wrappers +function signInUserPoolOIDC() { signInUserPool('enhanced'); } +function signInUserPoolBasic() { signInUserPool('basic'); } + +// Test functions - Generic function +async function testProvider(flowType, providerType, userToken = null) { + showAPIVisualizer(flowType); + const mockTokens = { 'Developer': userToken || 'mock-developer-user-id' }; + const token = mockTokens[providerType]; + if (window.flowManager) { + await window.flowManager.executeRealFlow(providerType, token, flowType); + } +} + +// Backward compatible wrappers +async function testEnhancedProvider(providerType, userToken = null) { + await testProvider('enhanced', providerType, userToken); +} + +async function testBasicProvider(providerType, userToken = null) { + await testProvider('basic', providerType, userToken); +} + +async function testEnhancedGuest() { + try { + console.log('Starting enhanced guest test...'); + console.log('API Endpoint:', CONFIG.apiEndpoint); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout + + const response = await fetch(CONFIG.apiEndpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ provider_type: 'Guest', provider_token: 'none', flow_type: 'enhanced' }), + signal: controller.signal + }); + + clearTimeout(timeoutId); + + console.log('Response status:', response.status); + console.log('Response ok:', response.ok); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + console.log('API result:', result); + + if (result.success) { + console.log('✓ Guest authentication successful!'); + console.log('You now have temporary AWS credentials for accessing AWS services.'); + console.log('To see detailed API flow, choose "View detailed API flow" button on your web interface.'); + showResult('Guest authentication successful!', result); + } else { + console.log('✗ Guest authentication failed:', result.error); + showResult(`Guest authentication failed: ${result.error}`, result); + } + } catch (error) { + console.error('testEnhancedGuest error:', error); + if (error.name === 'AbortError') { + showResult('Request timed out. Please try again.', null); + } else { + showResult(`Error: ${error.message}`, null); + } + } +} + +async function testBasicGuest() { + try { + console.log('Starting basic guest test...'); + console.log('API Endpoint:', CONFIG.apiEndpoint); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout + + const response = await fetch(CONFIG.apiEndpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ provider_type: 'Guest', provider_token: 'none', flow_type: 'basic' }), + signal: controller.signal + }); + + clearTimeout(timeoutId); + + console.log('Response status:', response.status); + console.log('Response ok:', response.ok); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + console.log('API result:', result); + + if (result.success) { + console.log('✓ Guest authentication successful!'); + console.log('You now have temporary AWS credentials for accessing AWS services.'); + console.log('To see detailed API flow, choose "View detailed API flow" button on your web interface.'); + showResult('Guest authentication successful!', result); + } else { + console.log('✗ Guest authentication failed:', result.error); + showResult(`Guest authentication failed: ${result.error}`, result); + } + } catch (error) { + console.error('testBasicGuest error:', error); + if (error.name === 'AbortError') { + showResult('Request timed out. Please try again.', null); + } else { + showResult(`Error: ${error.message}`, null); + } + } +} + +// Core API functions +async function callEnhancedFlow(providerType, token, resultElementId) { + try { + const response = await fetch(CONFIG.apiEndpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ provider_type: providerType, provider_token: token }) + }); + + const result = await response.json(); + + if (result.success) { + showResult(`${providerType} authentication successful!`, result); + } else { + showResult(`${providerType} authentication failed: ${result.error}`, null); + } + } catch (error) { + showResult(`Error: ${error.message}`, null); + } +} + +async function exchangeCodeForTokens(authCode) { + try { + const response = await fetch(`${getBaseUrl()}/auth/userpool/callback`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + code: authCode, + redirect_uri: window.location.origin + '/' + }) + }); + + const result = await response.json(); + + console.log('Token exchange result:', result); + + if (result.note && result.note.includes('WAF bypass')) { + console.log('WAF bypass successful - proceeding with normal flow'); + } + + if (result.error) { + showResult(`⚠️ ${result.message || result.error}`, null); + return; + } + + if (result.id_token) { + const flowType = sessionStorage.getItem('flow_type') || 'enhanced'; + + const authResponse = await fetch(CONFIG.apiEndpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + provider_type: 'UserPool', + provider_token: result.id_token, + flow_type: flowType + }) + }); + + const authResult = await authResponse.json(); + + if (authResult.success) { + // Redirect like other providers with success result + const resultParam = encodeURIComponent(JSON.stringify(authResult)); + window.location.href = `${window.location.origin}/?userpool_auth=success&result=${resultParam}`; + } else { + showResult(`Identity Pool failed: ${authResult.error}`, null); + } + } + } catch (error) { + showResult(`Error: ${error.message}`, null); + } +} + +// Direct User Pool authentication - bypasses WAF completely +async function authenticateUserPoolDirect(username, password, flowType = 'enhanced') { + try { + const response = await fetch(`${getBaseUrl()}/api/userpool-direct-auth`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }) + }); + + const result = await response.json(); + + if (result.success) { + // Use the ID token with Identity Pool + const authResponse = await fetch(CONFIG.apiEndpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + provider_type: 'UserPool', + provider_token: result.tokens.IdToken, + flow_type: flowType + }) + }); + + const authResult = await authResponse.json(); + + if (authResult.success) { + // Redirect like other providers to show API flow + const resultParam = encodeURIComponent(JSON.stringify(authResult)); + window.location.href = `${window.location.origin}/?userpool_auth=success&result=${resultParam}`; + } else { + showResult(`Identity Pool failed: ${authResult.error}`, null); + } + } else { + showResult(`User Pool authentication failed: ${result.error}`, null); + } + } catch (error) { + showResult(`Error: ${error.message}`, null); + } +} + +// Tab switching functions that consolidates duplicate tab management logic into a single parameterized function +function switchTabUnified(flowType, isGuest = false) { + const tabPrefix = isGuest ? 'guest-' : ''; + const tabSelectors = isGuest ? + ['#guest-enhanced-tab', '#guest-basic-tab'] : + ['#enhanced-tab', '#basic-tab']; + + // Hide all relevant tabs + document.querySelectorAll(tabSelectors.join(', ')).forEach(tab => { + tab.classList.remove('active'); + }); + document.querySelectorAll('.tab-button').forEach(btn => { + btn.classList.remove('active'); + }); + + // Show selected tab + const targetTabId = tabPrefix + flowType + '-tab'; + const targetTab = document.getElementById(targetTabId); + if (targetTab) { + targetTab.classList.add('active'); + } + + if (event && event.target) { + event.target.classList.add('active'); + } +} + +// Backward compatible wrapper functions +function switchTab(flowType) { + switchTabUnified(flowType, false); +} + +function switchGuestTab(flowType) { + switchTabUnified(flowType, true); +} + +// SAML authentication +function signInSAML(flowType = 'enhanced') { + // Check if trying to use SAML with basic flow + if (flowType === 'basic') { + showModal({ + title: 'SAML Not Supported in Basic Flow', + message: 'SAML identity providers only work with the enhanced flow. Amazon Cognito requires the enhanced flow for SAML authentication because it handles role selection automatically through SAML assertions.' + }); + return; + } + + sessionStorage.setItem('track_api_flow', 'true'); + sessionStorage.setItem('flow_type', flowType); + sessionStorage.setItem('provider', 'saml'); + + // Redirect to backend SAML handler + window.location.href = 'http://localhost:8006/auth/saml'; +} + +// SAML basic flow - will show modal instead +function signInSAMLBasic() { + signInSAML('basic'); +} + +// Show API visualizer +function showAPIVisualizer(flowType) { + const visualizerId = flowType === 'enhanced' ? 'apiVisualizer' : 'apiVisualizerBasic'; + const visualizer = document.getElementById(visualizerId); + if (visualizer) { + visualizer.style.display = 'block'; + visualizer.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } +} \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/flow-visualizer.js b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/flow-visualizer.js new file mode 100644 index 00000000000..d6b5bc064d1 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/flow-visualizer.js @@ -0,0 +1,745 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +class APIFlowManager { + constructor() { + this.currentStep = 0; + this.steps = []; + this.isEnhanced = true; + } + + initFlow(flowType, isGuest = false, isDeveloper = false) { + this.isEnhanced = flowType === 'enhanced'; + this.isGuest = isGuest; + this.isDeveloper = isDeveloper; + + if (isDeveloper) { + // Developer authentication uses GetOpenIdTokenForDeveloperIdentity as first call + if (this.isEnhanced) { + this.steps = ['GetOpenIdTokenForDeveloperIdentity', 'GetCredentialsForIdentity', 'Enhanced flow success']; + } else { + this.steps = ['GetOpenIdTokenForDeveloperIdentity', 'AssumeRoleWithWebIdentity', 'Basic flow Success']; + } + } else { + // All other providers use standard flows + this.steps = this.isEnhanced ? + (isGuest ? ['GetId', 'GetCredentialsForIdentity', 'Enhanced flow success'] : ['Provider authentication', 'GetId', 'GetCredentialsForIdentity', 'Enhanced flow success']) : + (isGuest ? ['GetId', 'GetOpenIdToken', 'AssumeRoleWithWebIdentity', 'Basic flow success'] : ['Provider authentication', 'GetId', 'GetOpenIdToken', 'AssumeRoleWithWebIdentity', 'Basic flow Success']); + } + + this.currentStep = 0; + this.resetSteps(); + } + + resetSteps() { + const containerId = this.isEnhanced ? 'flowDiagram' : 'flowDiagramBasic'; + const container = document.getElementById(containerId); + if (!container) return; + + // Generate dynamic step HTML + this.generateStepHTML(container); + + container.querySelectorAll('.api-step').forEach((el, i) => { + el.className = 'api-step'; + if (i === 0) el.classList.add('current'); + }); + + container.querySelectorAll('.step-status').forEach((el, i) => { + el.textContent = 'waiting'; + el.className = 'step-status'; + if (i === 0) el.classList.add('current'); + }); + + container.querySelectorAll('.payload-content').forEach((el, i) => { + if (i === 0) { + el.innerHTML = '
Status:
Waiting for authentication...'; + } else { + el.innerHTML = '
Status:
Waiting for previous step to complete...'; + } + }); + } + + generateStepHTML(container) { + const payloadPrefix = this.isEnhanced ? 'payload' : 'payload-basic'; + + const stepsHTML = this.steps.map((stepName, index) => { + const apiCall = stepName.includes('()') ? stepName.replace('()', '') : stepName; + const isApiCall = stepName.includes('()') || stepName.includes('GetOpenIdTokenForDeveloperIdentity'); + + return ` +
+
${index + 1}
+
+
+
${isApiCall ? `${stepName}` : stepName}
+
waiting
+
+
+
Status:
+ ${index === 0 ? 'Waiting for authentication...' : 'Waiting for previous step to complete...'} +
+
+
+ `; + }).join(''); + + container.innerHTML = stepsHTML; + } + + async executeRealFlow(providerType, token, flowType) { + try { + const isGuest = providerType === 'Guest'; + const isDeveloper = providerType === 'Developer'; + + console.log('executeRealFlow - providerType:', providerType, 'isDeveloper:', isDeveloper, 'flowType:', flowType); + + // Skip provider authentication step for guest access and developer authentication + if (!isGuest && !isDeveloper) { + await this.executeStep(0, 'Provider Authentication'); + } + + const response = await fetch('http://localhost:8006/api/authenticate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + provider_type: providerType, + provider_token: token, + flow_type: flowType + }) + }); + + const result = await response.json(); + + if (result.success) { + if (flowType === 'enhanced') { + const isDeveloper = result.provider === 'Developer'; + if (isGuest) { + await this.executeStep(0, 'GetId', result); + await this.executeStep(1, 'GetCredentialsForIdentity', result); + await this.executeStep(2, 'Enhanced flow success', result); + } else if (isDeveloper) { + await this.executeStep(0, 'GetOpenIdTokenForDeveloperIdentity', result); + await this.executeStep(1, 'GetCredentialsForIdentity', result); + await this.executeStep(2, 'Enhanced flow success', result); + } else { + await this.executeStep(0, 'Provider Authentication', result); + await this.executeStep(1, 'GetId', result); + await this.executeStep(2, 'GetCredentialsForIdentity', result); + await this.executeStep(3, 'Enhanced flow success', result); + } + } else { + const isDeveloper = result.provider === 'Developer'; + if (isGuest) { + await this.executeStep(0, 'GetId', result); + await this.executeStep(1, 'GetOpenIdToken', result); + await this.executeStep(2, 'AssumeRoleWithWebIdentity', result); + await this.executeStep(3, 'Basic flow success', result); + } else if (isDeveloper) { + await this.executeStep(0, 'GetOpenIdTokenForDeveloperIdentity', result); + await this.executeStep(1, 'AssumeRoleWithWebIdentity', result); + await this.executeStep(2, 'Basic flow success', result); + } else { + await this.executeStep(0, 'Provider Authentication', result); + await this.executeStep(1, 'GetId', result); + await this.executeStep(2, 'GetOpenIdToken', result); + await this.executeStep(3, 'AssumeRoleWithWebIdentity', result); + await this.executeStep(4, 'Basic flow success', result); + } + } + + showResult(`${providerType} ${isGuest ? 'access' : 'authentication'} successful!`, result); + } else { + this.showError(result.error); + showResult(`${providerType} ${isGuest ? 'access' : 'authentication'} failed: ${result.error}`, result); + } + } catch (error) { + this.showError(error.message); + showResult(`Error: ${error.message}`, null); + } + } + + async showCompletedFlow(realResult) { + const flowType = (realResult.flow_type === 'basic_authenticated' || realResult.flow_type === 'basic_guest') ? 'basic' : 'enhanced'; + const isGuest = realResult.provider === 'Guest'; + const isDeveloper = realResult.provider === 'Developer'; + + this.initFlow(flowType, isGuest, isDeveloper); + + if (flowType === 'enhanced') { + if (isGuest) { + await this.executeStep(0, 'GetId', realResult); + await this.executeStep(1, 'GetCredentialsForIdentity', realResult); + await this.executeStep(2, 'Enhanced flow success', realResult); + } else if (isDeveloper) { + await this.executeStep(0, 'GetOpenIdTokenForDeveloperIdentity', realResult); + await this.executeStep(1, 'GetCredentialsForIdentity', realResult); + await this.executeStep(2, 'Enhanced flow success', realResult); + } else { + await this.executeStep(0, 'Provider Authentication', realResult); + await this.executeStep(1, 'GetId', realResult); + await this.executeStep(2, 'GetCredentialsForIdentity', realResult); + await this.executeStep(3, 'Enhanced flow success', realResult); + } + } else { + if (isGuest) { + await this.executeStep(0, 'GetId', realResult); + await this.executeStep(1, 'GetOpenIdToken', realResult); + await this.executeStep(2, 'AssumeRoleWithWebIdentity', realResult); + await this.executeStep(3, 'Basic flow Success', realResult); + } else if (isDeveloper) { + await this.executeStep(0, 'GetOpenIdTokenForDeveloperIdentity', realResult); + await this.executeStep(1, 'AssumeRoleWithWebIdentity', realResult); + await this.executeStep(2, 'Basic flow Success', realResult); + } else { + await this.executeStep(0, 'Provider Authentication', realResult); + await this.executeStep(1, 'GetId', realResult); + await this.executeStep(2, 'GetOpenIdToken', realResult); + await this.executeStep(3, 'AssumeRoleWithWebIdentity', realResult); + await this.executeStep(4, 'Basic flow Success', realResult); + } + } + + await this.addFlowCompletionActions(flowType); + } + + async executeStep(stepIndex, stepName, realData = null) { + const containerId = this.isEnhanced ? 'flowDiagram' : 'flowDiagramBasic'; + const container = document.getElementById(containerId); + if (!container) return; + + const steps = container.querySelectorAll('.api-step'); + const indicators = container.querySelectorAll('.step-indicator'); + const statuses = container.querySelectorAll('.step-status'); + const step = steps[stepIndex]; + const indicator = indicators[stepIndex]; + const status = statuses[stepIndex]; + + if (!step || !indicator || !status) return; + + steps.forEach(el => el.classList.remove('current', 'in-progress', 'complete')); + indicators.forEach(el => el.classList.remove('current', 'in-progress', 'complete')); + + step.classList.add('in-progress'); + indicator.classList.add('in-progress'); + status.textContent = 'in progress'; + status.className = 'step-status in-progress'; + + let delay; + switch (stepName) { + case 'Provider Authentication': delay = 2000; break; + case 'GetId': delay = 1200; break; + case 'GetCredentialsForIdentity': delay = 1800; break; + case 'GetOpenIdToken': delay = 1000; break; + case 'GetOpenIdTokenForDeveloperIdentity': delay = 1500; break; + case 'AssumeRoleWithWebIdentity': delay = 1500; break; + default: delay = 800; + } + + await new Promise(resolve => setTimeout(resolve, delay)); + + step.classList.remove('in-progress'); + step.classList.add('complete'); + indicator.classList.remove('in-progress'); + indicator.classList.add('complete'); + status.textContent = 'complete'; + status.className = 'step-status complete'; + + this.showRealPayload(stepIndex, realData); + + if (stepIndex + 1 < this.steps.length) { + steps[stepIndex + 1].classList.add('current'); + indicators[stepIndex + 1].classList.add('current'); + statuses[stepIndex + 1].className = 'step-status current'; + } + } + + async addFlowCompletionActions(flowType) { + await new Promise(resolve => setTimeout(resolve, 2000)); + + const containerId = flowType === 'enhanced' ? 'flowDiagram' : 'flowDiagramBasic'; + const container = document.getElementById(containerId); + if (!container) return; + + const isGuest = window.currentAuthResult?.provider === 'Guest'; + const replayFunction = isGuest ? 'replayGuestAPIFlowVisualization()' : 'replayAPIFlowVisualization()'; + + const actionsHTML = ` +
+
+

API flow visualization complete!

+

You've successfully seen how your ${flowType === 'enhanced' ? 'enhanced' : 'basic'} authentication flow works behind the scenes.

+
+ +
+ + +
+ +
+

What's next: You can replay this visualization to better understand the process, or try authenticating with a different provider to see how the flow varies.

+
+
+ `; + + container.insertAdjacentHTML('afterend', actionsHTML); + + setTimeout(() => { + const actionsElement = document.querySelector('.flow-completion-actions'); + if (actionsElement) { + actionsElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }, 300); + } + + showRealPayload(stepIndex, realData) { + const payloadId = this.isEnhanced ? `payload-${stepIndex}` : `payload-basic-${stepIndex}`; + const payloadEl = document.getElementById(payloadId); + if (!payloadEl) return; + + if (stepIndex === this.steps.length - 1) { + payloadEl.innerHTML = ` +
Status:
+ Authentication complete! + `; + } else if (realData) { + const stepName = this.steps[stepIndex]; + let requestData, responseData; + + switch (stepName) { + case 'Provider authentication': + console.log('Provider authentication step - realData:', realData); + console.log('Available keys:', Object.keys(realData || {})); + + // Skip this step for guest access and developer authentication + if (realData.provider === 'Guest' || realData.provider === 'Developer') { + requestData = realData.provider === 'Guest' ? 'No provider authentication required for guest access' : 'No provider authentication required for developer authentication'; + responseData = { message: realData.provider === 'Guest' ? 'Guest access - no external provider authentication needed' : 'Developer authentication - uses IAM-authenticated API calls' }; + break; + } + + const providerName = realData.provider || 'Google'; + + // Handle SAML authentication differently + if (providerName === 'SAML') { + requestData = `User authenticated with SAML provider
SAML Response received via HTTP POST
to: ${realData.callback_url || 'callback-url'}`; + responseData = { + saml_response: realData.provider_token ? realData.provider_token.substring(0, 50) + '...' : 'SAML-Response...', + provider_arn: realData.provider_key || 'SAML-Provider-ARN', + status: 'Authentication successful' + }; + break; + } + + // Use actual OAuth response if available + if (realData.oauth_response) { + console.log('Using actual OAuth response:', realData.oauth_response); + const endpoint = realData.token_endpoint || 'https://oauth2.googleapis.com/token'; + requestData = `POST ${endpoint}
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
client_id=${realData.client_id || 'your-client-id'}&
client_secret=***&
code=${realData.auth_code || 'auth-code-from-callback'}&
redirect_uri=http://localhost:8006/auth/${(realData.provider_name || providerName).toLowerCase()}/callback`; + + // Use actual OAuth response, truncating sensitive data + responseData = {}; + for (const [key, value] of Object.entries(realData.oauth_response)) { + if (typeof value === 'string' && value.length > 50) { + responseData[key] = value.substring(0, 30) + '...'; + } else { + responseData[key] = value; + } + } + } else { + console.log('Using fallback template data for:', providerName); + const providerUrls = { + 'Google': 'https://oauth2.googleapis.com/token', + 'Facebook': 'https://graph.facebook.com/v18.0/oauth/access_token', + 'Amazon': 'https://api.amazon.com/auth/o2/token', + 'OIDC': realData.oidc_endpoint || 'OIDC-Token-Endpoint', + 'UserPool': realData.token_endpoint || 'https://your-domain.auth.region.amazoncognito.com/oauth2/token' + }; + requestData = `POST ${providerUrls[providerName] || providerUrls.Google}
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
client_id=${realData.client_id || 'your-client-id'}&
client_secret=***&
code=${realData.auth_code || 'auth-code-from-callback'}&
redirect_uri=http://localhost:8006/auth/${providerName === 'UserPool' ? 'userpool' : providerName.toLowerCase()}/callback`; + + if (providerName === 'Facebook') { + responseData = { + access_token: 'EAABwzLixnjYBO...', + token_type: 'bearer', + expires_in: 5183999 + }; + } else if (providerName === 'Amazon') { + responseData = { + access_token: 'Atza|IwEBIA...', + token_type: 'bearer', + expires_in: 3600, + refresh_token: 'Atzr|IwEBIL...' + }; + } else { + responseData = { + access_token: 'ya29.a0AfH6SMC...', + id_token: 'eyJhbGciOiJSUzI1NiIs...', + token_type: 'Bearer', + expires_in: 3600, + scope: 'openid email profile' + }; + } + } + break; + case 'GetId': + if (realData.provider === 'Guest') { + requestData = `{
  \"IdentityPoolId\": \"${realData.identity_pool_id || 'us-east-2:your-identity-pool-id'}\"
}`; + responseData = { + IdentityId: realData.identity_id + }; + break; + } + + let getIdProviderKey = realData.provider_key; + if (!getIdProviderKey) { + if (realData.provider === 'Amazon') getIdProviderKey = 'www.amazon.com'; + else if (realData.provider === 'Facebook') getIdProviderKey = 'graph.facebook.com'; + else if (realData.provider === 'SAML') getIdProviderKey = realData.provider_key || 'SAML-Provider-ARN'; + else if (realData.provider === 'OIDC') getIdProviderKey = realData.provider_key || 'OIDC-Provider'; + else getIdProviderKey = 'accounts.google.com'; + } + requestData = `{
  "AccountId": "${realData.account_id || 'YOUR_ACCOUNT_ID'}",
  "IdentityPoolId": "${realData.identity_pool_id || 'us-east-2:your-identity-pool-id'}",
  "Logins": {
    "${getIdProviderKey}": "${realData.provider_token ? realData.provider_token.substring(0, 20) + '...' : 'token...'}",
  }
}`; + responseData = { IdentityId: realData.identity_id }; + break; + case 'GetCredentialsForIdentity': + if (realData.provider === 'Guest') { + requestData = `{
  "IdentityId": "${realData.identity_id}"
}`; + responseData = { + IdentityId: realData.identity_id, + Credentials: { + AccessKeyId: realData.credentials?.AccessKeyId || 'ASIA...', + SecretKey: realData.credentials?.SecretKey || realData.credentials?.SecretAccessKey || 'SecretKey...', + SessionToken: realData.credentials?.SessionToken ? realData.credentials.SessionToken.substring(0, 50) + '...' : 'IQoJb3JpZ2luX2VjE...', + Expiration: realData.credentials?.Expiration || new Date().toISOString() + } + }; + break; + } + + let getCredsProviderKey = realData.provider_key; + if (!getCredsProviderKey) { + if (realData.provider === 'Amazon') getCredsProviderKey = 'www.amazon.com'; + else if (realData.provider === 'Facebook') getCredsProviderKey = 'graph.facebook.com'; + else if (realData.provider === 'SAML') getCredsProviderKey = realData.provider_key || 'SAML-Provider-ARN'; + else if (realData.provider === 'OIDC') getCredsProviderKey = realData.provider_key || 'OIDC-Provider'; + else getCredsProviderKey = 'accounts.google.com'; + } + requestData = `{
  "IdentityId": "${realData.identity_id}",
  "Logins": {
    "${getCredsProviderKey}": "${realData.provider_token ? realData.provider_token.substring(0, 20) + '...' : 'token...'}",
  }
}`; + responseData = { + IdentityId: realData.identity_id, + Credentials: { + AccessKeyId: realData.credentials.AccessKeyId, + SecretAccessKey: realData.credentials.SecretAccessKey || realData.credentials.SecretKey, + SessionToken: realData.credentials.SessionToken ? realData.credentials.SessionToken.substring(0, 50) + '...' : 'token...' + } + }; + break; + case 'GetOpenIdToken': + // Handle guest access differently + if (realData.provider === 'Guest') { + requestData = `{
  "IdentityId": "${realData.identity_id}"
}`; + responseData = { + IdentityId: realData.identity_id, + Token: realData.open_id_token || 'eyJraWQiOiJhcGktZ3c...' + }; + break; + } + + requestData = `{
  "IdentityId": "${realData.identity_id}",
  "Logins": {
    "${realData.provider_key || 'accounts.google.com'}": "${realData.provider_token ? realData.provider_token.substring(0, 20) + '...' : 'token...'}",
  }
}`; + responseData = { + IdentityId: realData.identity_id, + Token: realData.open_id_token || 'eyJraWQiOiJhcGktZ3c...' + }; + break; + case 'GetOpenIdTokenForDeveloperIdentity': + requestData = `{
  "IdentityPoolId": "${realData.identity_pool_id || 'us-east-2:your-identity-pool-id'}",
  "Logins": {
    "${realData.provider_key || 'ExampleProvider01'}": "${realData.provider_token || 'developer-user-id'}",
  },
  "TokenDuration": 3600
}`; + responseData = { + IdentityId: realData.identity_id || 'us-east-2:identity-id', + Token: realData.open_id_token ? realData.open_id_token.substring(0, 30) + '...' : 'eyJhbGciOiJSUzI1NiIs...' + }; + break; + case 'AssumeRoleWithWebIdentity': + // Extract role ARN from actual credentials or use a default + const roleArn = realData.role_arn || (realData.provider === 'Guest' ? + 'arn:aws:iam::ACCOUNT_ID:role/Cognito_IdentityPoolUnauth_Role' : + 'arn:aws:iam::ACCOUNT_ID:role/Cognito_IdentityPoolAuth_Role'); + requestData = `{
  "RoleArn": "${roleArn}",
  "WebIdentityToken": "${realData.open_id_token ? realData.open_id_token.substring(0, 20) + '...' : 'eyJhbGciOiJSUzI1NiIs...'}"
}`; + responseData = { + Credentials: { + AccessKeyId: realData.credentials?.AccessKeyId || 'ASIA...', + SecretAccessKey: realData.credentials?.SecretAccessKey || realData.credentials?.SecretKey || 'SecretKey...', + SessionToken: realData.credentials?.SessionToken ? realData.credentials.SessionToken.substring(0, 50) + '...' : 'IQoJb3JpZ2luX2VjE...' + } + }; + break; + default: + requestData = 'API Request'; + responseData = { status: 'success' }; + } + + payloadEl.innerHTML = ` +
Request:
+ ${requestData}

+
Response:
+ ${JSON.stringify(responseData, null, 2).replace(/\n/g, '
  ')} + `; + } + } + + showError(errorMessage) { + const containerId = this.isEnhanced ? 'flowDiagram' : 'flowDiagramBasic'; + const container = document.getElementById(containerId); + if (!container) return; + + const currentStep = container.querySelector('.api-step.in-progress') || container.querySelector('.api-step.current'); + if (currentStep) { + const status = currentStep.querySelector('.step-status'); + const payload = currentStep.querySelector('.payload-content'); + + status.textContent = 'error'; + status.className = 'step-status error'; + payload.innerHTML = ` +
Error:
+ ${errorMessage} + `; + } + } +} + +// Initialize flow manager globally +window.flowManager = new APIFlowManager(); + +// Add API data for GetOpenIdTokenForDeveloperIdentity +if (typeof apiData !== 'undefined') { + apiData['GetOpenIdTokenForDeveloperIdentity'] = { + service: 'Amazon Cognito identity pool', + description: 'Returns an OpenID Connect token and identity ID for a developer authenticated identity. This is the entry point for developer authentication flows.', + purpose: 'IAM-authenticated API call (requires AWS credentials) that exchanges developer user identifiers for Cognito identity tokens. This is the secure starting point for developer authentication.', + docUrl: 'https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetOpenIdTokenForDeveloperIdentity.html' + }; +} + +// Functions needed by other modules +function showDetailedAPIFlow() { + if (!window.currentAuthResult) { + alert("No authentication data found. Please authenticate first."); + return; + } + + const result = window.currentAuthResult; + const flowType = result.flow_type === 'basic_authenticated' ? 'basic' : 'enhanced'; + const container = document.querySelector('.tab-content.active'); + + if (!container) { + alert("Container not found!"); + return; + } + + showAPIFlowExplanation(result, flowType, container); +} + +function showAPIFlowExplanation(result, flowType, container) { + const provider = result.provider || 'Unknown'; + const isBasicFlow = flowType === 'basic'; + const isGuest = provider === 'Guest'; + + const explanationHTML = ` +
+
+

API flow visualization

+

Understanding your ${isGuest ? 'guest access' : provider + ' authentication'} process

+
+ +
+
+

What you'll see:

+
    +
  • Step-by-step API calls that happened during your ${isGuest ? 'guest access' : 'authentication'}
  • +
  • Real request/response data from AWS Cognito services
  • +
+
+ +
+

Your ${isGuest ? 'guest access' : 'authentication'} flow:

+
+ ${isBasicFlow ? + (isGuest ? 'Basic guest flow (3 steps)' : 'Basic flow (4 steps)') : + (isGuest ? 'Enhanced guest flow (2 steps)' : 'Enhanced flow (3 steps)') + } +
+

+ ${isGuest ? + (isBasicFlow + ? 'Guest access using basic flow: GetId() → GetOpenIdToken() → AssumeRoleWithWebIdentity(). No external provider authentication required.' + : 'Guest access using enhanced flow: GetId() → GetCredentialsForIdentity(). No external provider authentication required.' + ) : + (isBasicFlow + ? 'The basic flow uses a 4-step process: Provider authentication → GetId() → GetOpenIdToken() → AssumeRoleWithWebIdentity(). This is the traditional method that provides more granular control.' + : 'The enhanced flow uses a streamlined 3-step process: Provider Authentication → GetId() → GetCredentialsForIdentity(). This is the recommended modern approach for better performance.' + ) + } +

+
+ +
+

What you'll learn:

+
    + ${isGuest ? + '
  • Understand how unauthenticated identities work in Cognito
  • See how guest users get temporary AWS credentials
  • ' : + '
  • Understand how OAuth tokens are exchanged for AWS credentials
  • See the difference between enhanced and basic authentication flows
  • ' + } +
  • Learn about AWS Cognito identity pool API calls
  • +
+
+ +
+

Note: The visualization will replay your actual authentication process with the real data that was used to get your AWS credentials.

+
+
+ +
+ + + +
+
+ `; + container.innerHTML = explanationHTML; + + setTimeout(() => { + container.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 100); +} + +function startAPIFlowVisualization() { + if (!window.currentAuthResult) { + alert("Authentication data lost. Please authenticate again."); + return; + } + + const result = window.currentAuthResult; + const flowType = (result.flow_type === 'basic_authenticated' || result.flow_type === 'basic_guest') ? 'basic' : 'enhanced'; + const isGuest = result.provider === 'Guest'; + const container = document.querySelector('.tab-content.active'); + + if (!container) return; + + container.innerHTML = ` +
+

Preparing API flow visualization...

+

Setting up the step-by-step demonstration of your ${isGuest ? 'guest access' : 'authentication'} process.

+
+
+ `; + + setTimeout(() => { + if (isGuest) { + const visualizerHTML = flowType === 'enhanced' ? getGuestEnhancedVisualizer() : getGuestBasicVisualizer(); + container.innerHTML = visualizerHTML; + } else { + container.innerHTML = getProviderCategories(flowType); + } + + const visualizerId = flowType === 'enhanced' ? 'apiVisualizer' : 'apiVisualizerBasic'; + const visualizer = document.getElementById(visualizerId); + + if (visualizer) { + visualizer.style.display = 'block'; + const isDeveloper = result.provider === 'Developer'; + window.flowManager.initFlow(flowType, isGuest, isDeveloper); + + setTimeout(() => { + visualizer.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 300); + + setTimeout(() => { + window.flowManager.showCompletedFlow(result); + }, 800); + } + }, 1500); +} + +function replayAPIFlowVisualization() { + if (!window.currentAuthResult) { + alert("No authentication data found. Please authenticate first."); + return; + } + + const existingActions = document.querySelector('.flow-completion-actions'); + if (existingActions) { + existingActions.remove(); + } + + const container = document.querySelector('.tab-content.active'); + if (container) { + const loadingHTML = ` +
+

Replaying API flow visualization...

+

Restarting the step-by-step demonstration of your authentication process.

+
+
+ `; + container.innerHTML = loadingHTML; + } + + setTimeout(() => { + startAPIFlowVisualization(); + }, 1500); +} + +function replayGuestAPIFlowVisualization() { + if (!window.currentAuthResult) { + alert("No authentication data found. Please authenticate first."); + return; + } + + const existingActions = document.querySelector('.flow-completion-actions'); + if (existingActions) { + existingActions.remove(); + } + + const result = window.currentAuthResult; + const flowType = (result.flow_type === 'basic_authenticated' || result.flow_type === 'basic_guest') ? 'basic' : 'enhanced'; + const container = document.querySelector('.tab-content.active'); + + if (container) { + container.innerHTML = ` +
+

Replaying API flow visualization...

+

Restarting the step-by-step demonstration of your guest access process.

+
+
+ `; + } + + setTimeout(() => { + const visualizerHTML = flowType === 'enhanced' ? getGuestEnhancedVisualizer() : getGuestBasicVisualizer(); + container.innerHTML = visualizerHTML; + + const visualizerId = flowType === 'enhanced' ? 'apiVisualizer' : 'apiVisualizerBasic'; + const visualizer = document.getElementById(visualizerId); + + if (visualizer) { + visualizer.style.display = 'block'; + const isDeveloper = result.provider === 'Developer'; + window.flowManager.initFlow(flowType, true, isDeveloper); + + setTimeout(() => { + visualizer.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 300); + + setTimeout(() => { + window.flowManager.showCompletedFlow(result); + }, 800); + } + }, 1500); +} \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/ui-display.js b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/ui-display.js new file mode 100644 index 00000000000..188247266f7 --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/js/ui-display.js @@ -0,0 +1,851 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Global variable to store current result - make it globally accessible +window.currentAuthResult = null; + +function showResult(message, result) { + console.log('showResult called with message:', message); + console.log('showResult called with result:', result); + console.log('result.credentials:', result?.credentials); + console.log('result.flow_type:', result?.flow_type); + + // Handle null result (error case) + if (!result) { + const container = document.querySelector('.tab-content.active') || document.getElementById('basic-tab') || document.getElementById('enhanced-tab'); + if (container) { + container.innerHTML = ` +
+

Error

+

${message}

+ +
+ `; + } + return; + } + + // Store result globally + window.currentAuthResult = result; + + // Force switch to appropriate tab if this is a basic flow result + if (result && (result.flow_type === 'basic_authenticated' || result.flow_type === 'basic_guest')) { + const isGuest = result.provider === 'Guest'; + + document.querySelectorAll('.tab-content').forEach(tab => { + if (tab && tab.classList) tab.classList.remove('active'); + }); + document.querySelectorAll('.tab-button').forEach(btn => { + if (btn && btn.classList) btn.classList.remove('active'); + }); + + if (isGuest) { + // For guest access, activate guest-basic-tab + const guestBasicTab = document.getElementById('guest-basic-tab'); + if (guestBasicTab && guestBasicTab.classList) guestBasicTab.classList.add('active'); + } else { + // For authenticated access, activate basic-tab + const basicTab = document.getElementById('basic-tab'); + if (basicTab && basicTab.classList) basicTab.classList.add('active'); + } + + const basicTabButton = document.querySelector('.tab-button[onclick*="basic"]') || document.querySelectorAll('.tab-button')[1]; + if (basicTabButton && basicTabButton.classList) basicTabButton.classList.add('active'); + } + + let container = document.querySelector('.tab-content.active') || document.getElementById('basic-tab') || document.getElementById('enhanced-tab'); + console.log('Container found:', container); + + if (!container) { + console.log('No container found, using document.body'); + container = document.body; + } + + console.log('Checking conditions:'); + console.log('result && result.credentials:', result && result.credentials); + console.log('flow_type check:', result && (result.flow_type === 'basic_authenticated' || result.flow_type === 'basic_guest')); + + if (result && result.credentials) { + // Scenario 1: Guest Access Success + if (result.flow_type === 'basic_guest' || result.flow_type === 'enhanced_guest') { + showAuthSuccessWithOptions(result, 'Guest'); + return; + } + if (result.flow_type === 'basic_authenticated' || result.flow_type === 'basic_authenticated') { + showAuthSuccessWithOptions(result, result.provider); + return; + } else { + container.innerHTML = ` +
+

Enhanced flow success!

+

Identity Provider: ${result.provider}

+

Authorization Flow: ${result.flow_type} (2-Step Process)

+

Step 1: GetId() → Identity ID: ${result.identity_id}

+

Step 2: GetCredentialsForIdentity() → AWS credentials

+

AWS credentials:

+
{
+  "IdentityId": "${result.identity_id}",
+  "Credentials": {
+    "AccessKeyId": "${result.credentials.AccessKeyId}",
+    "SecretAccessKey": "${result.credentials.SecretKey || result.credentials.SecretAccessKey}",
+    "SessionToken": "${result.credentials.SessionToken}",
+    "Expiration": "${result.credentials.Expiration}"
+  }
+}
+
+

Next Steps

+

You can use these credentials to:

+
    +
  • Make authenticated AWS API calls
  • +
  • Access AWS resources according to your IAM role permissions
  • +
  • Use the AWS SDK in your application
  • +
+
+ ${result.provider !== 'Guest' ? `` : ''} +
+ `; + setTimeout(() => handleScrollAnimation(), 100); + } + } else { + let errorDetails = ''; + if (result && result.error) { + errorDetails = ` +

Error: ${result.error}

+ ${result.details ? `

Details: ${result.details}

` : ''} + ${result.error_type ? `

Type: ${result.error_type}

` : ''} + `; + } + + // Handle OIDC configuration errors specially + if (result && result.error_type === 'configuration_error') { + container.innerHTML = ` +
+

⚙️ OIDC Setup Required

+

${result.error}

+ + +
+ `; + } else { + container.innerHTML = ` +
+

Authentication failed

+

${message}

+ ${errorDetails} + +
+ `; + } + } +} + +function showDetailedAPIFlow() { + if (!window.currentAuthResult) { + alert('No authentication result available'); + return; + } + + const result = window.currentAuthResult; + const flowType = result.flow_type === 'basic_guest' || result.flow_type === 'basic_authenticated' ? 'basic' : 'enhanced'; + + let container = document.querySelector('.tab-content.active') || document.getElementById('basic-tab') || document.getElementById('enhanced-tab'); + if (!container) { + container = document.body; + } + + container.innerHTML = getProviderCategories(flowType); + + const visualizerId = flowType === 'enhanced' ? 'apiVisualizer' : 'apiVisualizerBasic'; + const visualizer = document.getElementById(visualizerId); + + if (visualizer && window.flowManager) { + visualizer.style.display = 'block'; + window.flowManager.initFlow(flowType); + + setTimeout(() => { + visualizer.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 300); + + setTimeout(() => { + window.flowManager.showCompletedFlow(result); + }, 800); + } +} + +function showGuestDetailedAPIFlow() { + if (!window.currentAuthResult) { + alert('No authentication result available'); + return; + } + + const result = window.currentAuthResult; + const flowType = result.flow_type === 'basic_guest' || result.flow_type === 'basic_authenticated' ? 'basic' : 'enhanced'; + + let container = document.querySelector('.tab-content.active') || document.getElementById('basic-tab') || document.getElementById('enhanced-tab'); + if (!container) { + container = document.body; + } + + showAPIFlowExplanation(result, flowType, container); +} + +function getEnhancedVisualizer() { + return ` + + `; +} + +// Unified guest visualizer generation +function getGuestVisualizerUnified(flowType, isGuest = true) { + const flowConfig = { + enhanced: { + steps: ['GetId()', 'GetCredentialsForIdentity()', 'Enhanced flow Success'], + title: 'Enhanced flow progress', + visualizerId: 'apiVisualizer', + diagramId: 'flowDiagram', + payloadPrefix: 'payload' + }, + basic: { + steps: ['GetId()', 'GetOpenIdToken()', 'AssumeRoleWithWebIdentity()', 'Basic flow Success'], + title: 'Basic flow progress', + visualizerId: 'apiVisualizerBasic', + diagramId: 'flowDiagramBasic', + payloadPrefix: 'payload-basic' + } + }; + + const config = flowConfig[flowType]; + const guestSuffix = isGuest ? ' (Guest Access)' : ''; + + const stepsHtml = config.steps.map((stepName, index) => ` +
+
${index + 1}
+
+
+
${stepName}
+
waiting
+
+
+
Status:
+ ${index === 0 ? 'Waiting for authentication...' : 'Waiting for previous step to complete...'} +
+
+
`).join(''); + + return ` + + `; +} + +// Backward compatible wrapper functions +function getGuestEnhancedVisualizer() { + return getGuestVisualizerUnified('enhanced', true); +} + +function getGuestBasicVisualizer() { + return getGuestVisualizerUnified('basic', true); +} + +function getBasicVisualizer() { + return ` + + `; +} + +function getProviderCategories(flowType) { + if (flowType === 'enhanced') { + return ` +
+
+

Social identity providers

+

Amazon Cognito provides direct integration with Login with Amazon, Sign in with Apple, Google, Facebook, and Twitter. Provide information about your developer app or project to your identity pools, and Amazon Cognito trusts OAuth 2.0 tokens that the social provider issues on behalf of your app.

+
+
+ Sign in with Google +
+
+ Sign in with Facebook +
+
+ Login with Amazon +
+
+
+ +
+

Enterprise providers (OIDC or SAML)

+

You can trust authenticated claims from any identity provider that issues SAML 2.0 assertions or OIDC tokens. Amazon Cognito integrates with identity providers that you have configured in AWS Identity and Access Management (IAM).

+
+
+ OpenID Connect (OIDC) +
+
+ SAML 2.0 +
+
+
+ +
+

Custom developer provider

+

Issue credentials to users who authenticate with your own developer provider.

+
+
+ Custom developer provider +
+
+
+ +
+

Amazon Cognito user pool

+

Issue credentials to users who authenticate through an Amazon Cognito user pool. Your users can sign in to a user pool using the built-in user directory or through a third-party identity provider.

+
+
+ Cognito user pool +
+
+
+
+ + `; + } else { + return ` +
+
+

Social identity providers

+

Amazon Cognito provides direct integration with Login with Amazon, Sign in with Apple, Google, Facebook, and Twitter. Provide information about your developer app or project to your identity pools, and Amazon Cognito trusts OAuth 2.0 tokens that the social provider issues on behalf of your app.

+
+
+ Sign in with Google +
+
+ Sign in with Facebook +
+
+ Login with Amazon +
+
+
+ +
+

Enterprise providers (OIDC or SAML)

+

You can trust authenticated claims from any identity provider that issues SAML 2.0 assertions or OIDC tokens. Amazon Cognito integrates with identity providers that you have configured in AWS Identity and Access Management (IAM).

+
+
+ OpenID Connect (OIDC) +
+
+ SAML 2.0 +
+
+
+ +
+

Custom developer provider

+

Issue credentials to users who authenticate with your own developer provider.

+
+
+ Custom developer provider +
+
+
+ +
+

Amazon Cognito user pool

+

Issue credentials to users who authenticate through an Amazon Cognito user pool. Your users can sign in to a user pool using the built-in user directory or through a third-party identity provider.

+
+
+ Cognito user pool +
+
+
+
+ + `; + } +} + +// Trigger animations after dynamic content is loaded +function triggerAnimationsAfterLoad() { + setTimeout(() => { + if (typeof handleScrollAnimation === 'function') { + handleScrollAnimation(); + } + }, 100); +} + + + +// Success options after OAuth completion +function showAuthSuccessWithOptions(result, provider) { + window.currentAuthResult = result; + + const isGuest = provider === 'Guest'; + const isBasicFlow = result && (result.flow_type === 'basic_authenticated' || result.flow_type === 'basic_guest'); + + // Force switch to appropriate tab + if (isBasicFlow) { + document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active')); + document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active')); + + if (isGuest) { + // For guest access, use guest-basic-tab + const guestBasicTab = document.getElementById('guest-basic-tab'); + if (guestBasicTab) { + guestBasicTab.classList.add('active'); + } + // Also activate the basic tab button + const basicTabButton = document.querySelector('.tab-button[onclick*="basic"]') || document.querySelectorAll('.tab-button')[1]; + if (basicTabButton) basicTabButton.classList.add('active'); + } else { + // For authenticated access, use basic-tab + const basicTab = document.getElementById('basic-tab'); + if (basicTab) { + basicTab.classList.add('active'); + } + const basicTabButton = document.querySelector('.tab-button[onclick*="basic"]') || document.querySelectorAll('.tab-button')[1]; + if (basicTabButton) basicTabButton.classList.add('active'); + } + } + + // Get the active container + let container = document.querySelector('.tab-content.active'); + + // Fallback to appropriate tab if no active container found + if (!container) { + if (isGuest && isBasicFlow) { + container = document.getElementById('guest-basic-tab'); + } else if (isGuest) { + container = document.getElementById('guest-enhanced-tab'); + } else if (isBasicFlow) { + container = document.getElementById('basic-tab'); + } else { + container = document.getElementById('enhanced-tab'); + } + } + + // Final fallback + if (!container) { + container = document.body; + } + + container.innerHTML = ` +
+

${provider.charAt(0).toUpperCase() + provider.slice(1)} authentication successful!

+

You now have temporary AWS credentials for accessing AWS services.

+ +
+ + + ${!isGuest ? `` : ''} +
+
+ `; + setTimeout(() => handleScrollAnimation(), 100); +} + +function showJustCredentials() { + if (!currentAuthResult) return; + const result = currentAuthResult; + const container = document.querySelector('.tab-content.active'); + + const credentialsJson = { + "IdentityId": result.identity_id, + "Credentials": { + "AccessKeyId": result.credentials.AccessKeyId, + "SecretAccessKey": result.credentials.SecretAccessKey || result.credentials.SecretKey, + "SessionToken": result.credentials.SessionToken, + "Expiration": result.credentials.Expiration + } + }; + + container.innerHTML = ` +
+

AWS credentials

+

Your temporary AWS credentials for accessing AWS services:

+ +
${JSON.stringify(credentialsJson, null, 2)}
+ +
+

Next Steps

+

You can use these credentials to:

+
    +
  • Make authenticated AWS API calls
  • +
  • Access AWS resources according to your IAM role permissions
  • +
  • Use with AWS SDK in your applications
  • +
+
+ +
+ + ${result.provider !== 'Guest' ? `` : ''} +
+
+ `; + setTimeout(() => handleScrollAnimation(), 100); +} + +function showDeveloperAuthForm(flowType) { + const container = flowType === 'enhanced' ? document.getElementById('enhanced-tab') : document.getElementById('basic-tab'); + + container.innerHTML = ` +
+
+

You are authenticating with custom developer provider

+

Step 1: To proceed with custom developer provider authentication, first enter a user identifier for your custom developer provider. This simulates authenticating a user through your own authentication system.

+ +
+
+ + + +
+ + + + + +
+
+
+
+
+ `; +} + +async function authenticateDeveloperUser(flowType, userId) { + try { + const response = await fetch('http://localhost:8006/api/authenticate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + provider_type: 'Developer', + provider_token: userId, + flow_type: flowType + }) + }); + + const result = await response.json(); + window.currentAuthResult = result; + return result; + } catch (error) { + return { success: false, error: error.message }; + } +} + +async function handleDeveloperDetailedFlow(flowType) { + const userIdInput = document.getElementById('developerUserId'); + const userId = userIdInput.value.trim(); + + if (!userId) { + alert('Please enter a user identifier'); + return; + } + + // Authenticate in background + const result = await authenticateDeveloperUser(flowType, userId); + + if (result.success) { + showDeveloperAPIFlowExplanation(result, flowType); + } else { + showResult(`Developer authentication failed: ${result.error}`, result); + } +} + +async function handleDeveloperCredentialsOnly(flowType) { + const userIdInput = document.getElementById('developerUserId'); + const userId = userIdInput.value.trim(); + + if (!userId) { + alert('Please enter a user identifier'); + return; + } + + // Authenticate in background + const result = await authenticateDeveloperUser(flowType, userId); + + if (result.success) { + showResult('Developer authentication successful!', result); + } else { + showResult(`Developer authentication failed: ${result.error}`, result); + } +} + +function showDeveloperAPIFlowExplanation(result, flowType) { + const container = document.querySelector('.tab-content.active'); + const isBasicFlow = flowType === 'basic'; + + const explanationHTML = ` +
+
+

API flow visualization

+

Understanding Your Developer Authentication Process

+
+ +
+
+

What you'll see:

+
    +
  • Step-by-step API calls that happened during your authentication
  • +
  • Real request/response data from AWS Cognito services
  • +
+
+ +
+

Your authentication flow:

+
+ ${isBasicFlow ? 'Basic flow (3 steps)' : 'Enhanced flow (2 steps)'} +
+

+ ${isBasicFlow + ? 'Developer authentication with basic flow uses: GetOpenIdTokenForDeveloperIdentity() → GetOpenIdToken() → AssumeRoleWithWebIdentity(). This provides granular control over the authentication process.' + : 'Developer authentication with enhanced flow uses: GetOpenIdTokenForDeveloperIdentity() → GetCredentialsForIdentity(). This is a streamlined approach for custom developer providers.' + } +

+
+ +
+

What you'll learn:

+
    +
  • How custom developer providers integrate with AWS Cognito
  • +
  • The difference between developer authentication and social providers
  • +
  • AWS API calls specific to developer authenticated identities
  • +
+
+ +
+

Note: The visualization will replay your actual authentication process with the real data that was used to get your AWS credentials.

+
+
+ +
+ + + +
+
+ `; + + container.innerHTML = explanationHTML; +} + +function startDeveloperAPIVisualization(flowType) { + if (!window.currentAuthResult) { + alert('Authentication data lost. Please try again.'); + return; + } + + const container = document.querySelector('.tab-content.active'); + + container.innerHTML = ` +
+

Preparing API flow visualization...

+

Setting up the step-by-step demonstration of your developer authentication process.

+
+
+ `; + + setTimeout(() => { + container.innerHTML = getProviderCategories(flowType); + + const visualizerId = flowType === 'enhanced' ? 'apiVisualizer' : 'apiVisualizerBasic'; + const visualizer = document.getElementById(visualizerId); + + if (visualizer) { + visualizer.style.display = 'block'; + window.flowManager.initFlow(flowType); + + setTimeout(() => { + visualizer.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 300); + + setTimeout(() => { + window.flowManager.showCompletedFlow(window.currentAuthResult); + }, 800); + } + }, 1500); +} + +function resetToProviderSelection() { + const enhancedTab = document.getElementById('enhanced-tab'); + const basicTab = document.getElementById('basic-tab'); + + if (enhancedTab) { + enhancedTab.innerHTML = getProviderCategories('enhanced'); + } + if (basicTab) { + basicTab.innerHTML = getProviderCategories('basic'); + } + + // Reset any global state + window.currentAuthResult = null; +} \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/package.json b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/package.json new file mode 100644 index 00000000000..7ce7fd82d2b --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/frontend/package.json @@ -0,0 +1,17 @@ +{ + "name": "@amzn/cognito-identity-pools-demo", + "version": "1.0.0", + "description": "Amazon Cognito Identity Pools Enhanced vs Basic Flow Demo", + "dependencies": { + "aws-sdk": "^2.1400.0", + "amazon-cognito-identity-js": "^6.3.12" + }, + "scripts": { + "build": "npm run bundle", + "bundle": "echo 'Bundling dependencies for browser use'" + }, + "npm-pretty-much": { + "appName": "cognito-demo", + "runTest": "never" + } +} \ No newline at end of file diff --git a/python/example_code/cognito/scenarios/identity_pools_example_demo/web/requirements.txt b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/requirements.txt new file mode 100644 index 00000000000..5429971806f --- /dev/null +++ b/python/example_code/cognito/scenarios/identity_pools_example_demo/web/requirements.txt @@ -0,0 +1,4 @@ +boto3>=1.26.0 +requests>=2.28.0 +python-dotenv>=0.19.0 +PyJWT>=2.4.0 \ No newline at end of file