【With Step-by-Step Images】How to Use Active Directory Users for Amazon Cognito Authentication

How to Use Active Directory Users for Amazon Cognito Authentication
  • Utilize Active Directory users for Amazon Cognito authentication to enable secure and efficient access management.
  • Seamlessly integrate Amazon Cognito with Active Directory via IAM Identity Center.
  • Introduce a method to set up user authentication for executing target Lambda functions with ALB.

Background

To reduce AWS Client VPN costs, we aimed to enable employees to associate AWS Client VPN connections themselves as needed.
As a solution, we decided to execute the association via a Lambda function and allow all employees to execute this Lambda function.

However, to prevent unauthorized individuals from executing the function, we implemented user authentication for Lambda execution.
Our company manages employee user IDs with Active Directory, as described in the following article.
We began exploring the possibility of using Active Directory users for authentication.

Failure

Before diving into the actual configuration, let me share the failed attempt for reference.

The first idea was to use API Gateway to execute the Lambda function and utilize Cognito for authentication in API Gateway.
We thought that if the callback URL for Cognito was set to API Gateway, users could be authenticated via Cognito's sign-in URL, enabling API Gateway to execute requests.

However, there was a problem.
After successfully authenticating through Cognito's sign-in URL, the id_token is appended as a query parameter in the callback URL.
In contrast, API Gateway requires the token to be included in the Authorization header for authentication.

In other words, even if API Gateway is specified as the callback URL in Cognito, authentication in API Gateway would not succeed.
While it seemed feasible to use custom authentication in the Lambda function instead of Cognito, it would require significant development effort, so we decided to put it on hold for now.

Failed Configuration

Solution

As an alternative configuration, we decided to use ALB instead of API Gateway.
ALB also supports user authentication using Cognito.
Additionally, by specifying a Lambda function as the target, it is possible to send requests and execute the Lambda function after user authentication.

Furthermore, Cognito can be integrated with IAM Identity Center for user management.
IAM Identity Center allows specifying Active Directory as a source, enabling its use for Cognito authentication through IAM Identity Center.

For more information on integrating IAM Identity Center with an Amazon Cognito user pool, refer to:

How do I use an Amazon Cognito user pool to integrate IAM Identity Center?
https://repost.aws/knowledge-center/cognito-user-pool-iam-integration

The configuration is as follows:

Administrator’s Procedure

We have already completed the integration of IAM Identity Center with Active Directory in a separate article.
Thus, the actual work described here focuses on the configuration of Amazon Cognito and ALB.

For steps already completed (highlighted in blue), we provide an overview of their purpose and reference the detailed instructions in the following article:

Integrating Active Directory with IAM Identity Center

IAM Identity Center is a service that connects users to AWS services.
While users can be created directly within IAM Identity Center, it is also possible to synchronize users from Active Directory.
In our company, we have already synchronized Active Directory users with IAM Identity Center for a different purpose.
For detailed steps, please refer to the following article.

Preparing the Domain

ALB's Cognito authentication requires an HTTPS listener, so a public certificate must be issued via ACM.
If you already own a domain, the process becomes simpler. For this example, we acquired and used awsexample.com.

  1. Access the following URL to acquire a domain via Route 53:
    https://us-east-1.console.aws.amazon.com/route53/domains/home?region=us-east-1#/DomainSearch
  2. Check if your desired domain is available:
    • Check availability for a domain: awsexample.com
    • If available, select the domain and proceed to checkout.
Image
  1. [Step 1] Pricing
    • Duration: 1 year
    • Auto-renew: ☑️ On
Image
  1. [Step 2] Contact Information
    • Enter contact details in alphabetical characters.
    • Privacy protection: ☑️
      • Enabled (to keep contact details private).
Image
  1. [Step 3] Review and Submit
Image
  1. Once the domain is acquired, confirm that its public hosted zone has been created:
    https://us-east-1.console.aws.amazon.com/route53/v2/hostedzones?region=us-east-1#

Issuing a Certificate via ACM

  1. Request a public certificate via ACM:
    https://us-east-1.console.aws.amazon.com/acm/home?region=us-east-1#/certificates/request/public
    • Fully qualified domain name: api.awsexample.com
      • This is the domain name employees will use to access the system.
    • Validation method: ⦿ DNS validation - recommended
Image
  1. Click [Create records in Route 53] to automatically generate a validation record in the hosted zone of awsexample.com.
    • Once validation is complete, the certificate will be issued.
Image

Creating Cognito

  1. Create a Cognito User Pool using the following URL:
    https://us-east-1.console.aws.amazon.com/cognito/v2/idp/set-up-your-application?region=us-east-1
    • Name your application: ALB for Client VPN
    • Options for sign-in identifiers: ☑️ Username
    • Required attributes for sign-up: email
    • Return URL: https://api.awsexample.com/oauth2/idpresponse
      • Append /oauth2/idpresponse to the domain name you plan to assign to the ALB.
Image
  1. Navigate to [Overview] and [Domain] in the Amazon Cognito navigation pane to verify the User Pool ID and Cognito Domain.
    These details are required later for integration with IAM Identity Center and creating listeners in ALB.
    • User pool ID: us-east-1_eXAMPLE
    • Cognito domain: https://us-east-1example.auth.us-east-1.amazoncognito.com
Iamge

Preparing for IAM Identity Center Integration

  1. Create an application in IAM Identity Center using the following URL:
    https://us-east-1.console.aws.amazon.com/singlesignon/applications/home?region=us-east-1#/add
  2. [Step 1] Select application type
    • Setup preference: I have an application I want to set up
    • Application type: SAML 2.0
Image
  1. [Step 2] Configure application
    • Display name: Amazon Cognito
    • Description: Custom SAML 2.0 application for Amazon Cognito
    • Application metadata: ⦿ Manually type your metadata values
      • Application ACS URL: https://us-east-1example.auth.us-east-1.amazoncognito.com/saml2/idpresponse
        • Append /saml2/idpresponse to the Cognito Domain verified in Creating Cognito [Step 2].
      • Application SAML audience: urn:amazon:cognito:sp:us-east-1_eXAMPLE
        • Use the User Pool ID verified in Creating Cognito [Step 2].
  • At this point, download or memo the IAM Identity Center SAML Metadata File.
    This file will be required later for integration with Cognito.
    • IAM Identity Center SAML Metadata File:
      https://portal.sso.us-east-1.amazonaws.com/saml/metadata/ExAmPlExAmPlExAmPlExAmPlExAmPlExAmPlExAmPlE
Image
  1. From the details of the created application, go to [Actions] > [Edit attribute mappings] and configure attributes.
User attribute in the applicationMaps to this string value or user attribute in IAM Identity CenterFormat
Subject${user:subject}persistent
email${user:email}basic
Image
  1. From the details of the created application, assign groups via [Assign Users and Groups].
    The groups assigned here will successfully authenticate with Amazon Cognito.
    In this case, assign the group IAMIdentityCenterAllUsersAndGroups, which includes all employees.
Image

Preparing Cognito Integration

  1. Add IAM Identity Center as an identity provider using the following URL:
    https:/us-east-1.console.aws.amazon.com/cognito/v2/idp/user-pools/us-east-1_eXAMPLE/authentication/social-and-custom-providers/add?region=us-east-1
    • Identity provider: SAML
    • Provider name: IAMIdentityCenter
    • Metadata document source: ⦿ Enter metadata document endpoint URL
      • https://portal.sso.ap-northeast-1.amazonaws.com/saml/metadata/ExAmPlExAmPlExAmPlExAmPlExAmPlExAmPlExAmPlE
      • Use the IAM Identity Center SAML Metadata File verified in Preparing for IAM Identity Center Integration [Step 3].
    • Map attributes between your SAML provider and your user pool:
      • User pool attribute: email
      • SAML attribute: email
Image
  1. Change the identity provider for the application client (ALB for Client VPN) to IAMIdentityCenter using the following URL:
    https://us-east-1.console.aws.amazon.com/cognito/v2/idp/user-pools/us-east-1_eXAMPLE/applications/app-clients/<App Client ID>/login-pages/edit/hosted-ui-settings?region=us-east-1
    • Replace ap-northeast-1_eXAMPLE with the User Pool ID verified in Creating Cognito [Step 2].
    • Replace <App Client ID> with the App Client ID.
    • Identity Provider: IAMIdentityCenter
Image

Creating the Lambda Function

Next, create the Lambda function that will serve as the ALB's target.

  1. Execute the following AWS CLI command to create an IAM role for the Lambda function:
# Create IAM role
aws iam create-role \
--role-name LambdaRole-ClientVPN \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}'

# Attach the IAM policy required for Client VPN
aws iam attach-role-policy \
--role-name LambdaRole-ClientVPN \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess

# Attach the IAM policy required for logging
aws iam attach-role-policy \
--role-name LambdaRole-ClientVPN \
--policy-arn arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
  1. Create a Lambda function using the following URL:
    https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/create/function
  2. Use the following settings for the function:
    • Function name: AWSClientVPN-AssociatingSubnets
    • Runtime: Python 3.13
    • Execution role: LambdaRole-ClientVPN
Image
  1. Update the timeout duration to 1 minute using the following URL:
    https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/functions/AWSClientVPN-AssociatingSubnets/edit/basic-settings?tab=configure
    • Timeout: 1 min
Image
  1. Add the code for the Lambda function.
    Once the code is entered, it must be deployed by clicking Deploy or using the shortcut Ctrl+Shift+U.
    Most of the code was generated using ChatGPT, and the following is the code:
import boto3
import os
import json

def lambda_handler(event, context):

    # Initialize the EC2 client
    ec2 = boto3.client('ec2')
    
    # Input parameters
    client_vpn_endpoint_id = '<Your Client VPN endpoint ID>'
    subnet_id = '<Your subnet ID for associating>'
    
    try:
        # Describe the Client VPN endpoint to check its status
        endpoint_status_response = ec2.describe_client_vpn_endpoints(
            ClientVpnEndpointIds=[client_vpn_endpoint_id]
        )
        
        endpoint_status = endpoint_status_response['ClientVpnEndpoints'][0]['Status']['Code']
        print(f"Client VPN Endpoint Status: {endpoint_status}")
        
        # Check if the status is 'pending-associate'
        if endpoint_status.lower() == 'pending-associate':
            # Associate the Client VPN endpoint with the target subnet
            response = ec2.associate_client_vpn_target_network(
                ClientVpnEndpointId=client_vpn_endpoint_id,
                SubnetId=subnet_id
            )
            
            # Return response details
            return {
                'statusCode': 200,
                'headers': {
                    'Content-Type': 'application/json'
                },
                'body': json.dumps({
                    'Message': 'Successfully associated Client VPN endpoint with subnet.',
                    'AssociationId': response['AssociationId'],
                    'Status': response['Status']
                })
            }
        else:
            return {
                'statusCode': 400,
                'headers': {
                    'Content-Type': 'application/json'
                },
                'body': json.dumps({
                    'Message': f'Client VPN Endpoint is not in pending-associate state. Current status: {endpoint_status}'
                })
            }
        
    except Exception as e:
        print(f"Error associating Client VPN endpoint: {str(e)}")
        return {
            'statusCode': 500,
            'headers': {
                'Content-Type': 'application/json'
            },
            'body': json.dumps({
                'Message': 'Failed to associate Client VPN endpoint.',
                'Error': str(e)
            })
        }

Creating an ALB

  1. Start by creating the target group using AWS CLI.
    (Note: If you create the target group via the management console, the resource-based policy is created automatically, so the third AWS CLI command below is not required.)
LAMBDA_FUNCTION_NAME="AWSClientVPN-AssociatingSubnets"

# Create a target group
TARGET_GROUP_ARN=$(aws elbv2 create-target-group \
--name AssociatingSubnets-alb \
--target-type lambda \
--query 'TargetGroups[0].TargetGroupArn' \
--output text)

# Get the ARN of the Lambda function
LAMBDA_FUNCTION_ARN=$(aws lambda get-function \
--function-name $LAMBDA_FUNCTION_NAME \
--query 'Configuration.FunctionArn' \
--output text)

# Add permission for the ALB to invoke the Lambda function
aws lambda add-permission \
--function-name $LAMBDA_FUNCTION_NAME \
--statement-id "AWS-ALB_Invoke-targetgroup" \
--action lambda:InvokeFunction \
--principal elasticloadbalancing.amazonaws.com \
--source-arn $TARGET_GROUP_ARN

# Register the Lambda function
aws elbv2 register-targets \
--target-group-arn $TARGET_GROUP_ARN \
--targets Id=$LAMBDA_FUNCTION_ARN
  1. Create a security group for the ALB that allows HTTPS traffic:
VPC_ID="<Your VPC ID>"

# Create a security group for the ALB
SECURITY_GROUP_ID=$(aws ec2 create-security-group \
--group-name ALB-sg \
--description "Security group for ALB allowing HTTPS traffic" \
--vpc-id $VPC_ID \
--query 'GroupId' \
--output text)

# Add an inbound rule to allow HTTPS traffic
aws ec2 authorize-security-group-ingress \
--group-id $SECURITY_GROUP_ID \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0
  1. Create an ALB and specify at least two subnets in different availability zones:
PUBLIC_SUBNET1="<1st public subnet ID>"
PUBLIC_SUBNET2="<2nd public subnet ID>"

# Create an ALB
LOAD_BALANCER_ARN=$(aws elbv2 create-load-balancer \
--name AssociatingSubnets-alb \
--subnets $PUBLIC_SUBNET1 $PUBLIC_SUBNET2 \
--security-groups $SECURITY_GROUP_ID \
--scheme internet-facing \
--type application \
--ip-address-type ipv4 \
--query 'LoadBalancers[0].LoadBalancerArn' \
--output text)
  1. Create an HTTPS listener with Cognito authentication:
DOMAIN="api.awsexample.com"
USER_POOL_ID="ap-northeast-1_eXAMPLE"
COGNITO_DOMAIN="https://ap-northeast-1example.auth.ap-northeast-1.amazoncognito.com"
COGNITO_DOMAIN_PREFIX=$(echo $COGNITO_DOMAIN | awk -F[/:] '{print $4}' | cut -d. -f1)

# Get the ARN of the ACM certificate
CERTIFICATE_ARN=$(aws acm list-certificates \
--query "CertificateSummaryList[?DomainName=='${DOMAIN}'].CertificateArn" \
--output text)

# Get the ARN of the Cognito User Pool
COGNITO_USER_POOL_ARN=$(aws cognito-idp describe-user-pool \
--user-pool-id $USER_POOL_ID \
--query 'UserPool.Arn' \
--output text)

# Get the App Client ID for the User Pool
COGNITO_APP_CLIENT_ID=$(aws cognito-idp list-user-pool-clients \
--user-pool-id $USER_POOL_ID \
--query 'UserPoolClients[0].ClientId' \
--output text)

# Create an HTTPS listener with Cognito authentication
aws elbv2 create-listener \
--load-balancer-arn $LOAD_BALANCER_ARN \
--protocol HTTPS \
--port 443 \
--certificates CertificateArn=$CERTIFICATE_ARN \
--default-actions Type=authenticate-cognito,Order=1,AuthenticateCognitoConfig="{UserPoolArn=$COGNITO_USER_POOL_ARN,UserPoolClientId=$COGNITO_APP_CLIENT_ID,UserPoolDomain=$COGNITO_DOMAIN_PREFIX}" Type=forward,Order=2,TargetGroupArn=$TARGET_GROUP_ARN
  1. Finally, create a Route 53 record in the hosted zone to route traffic to the ALB when users access api.awsexample.com.
    Refer to the official documentation to verify the hosted zone ID for the ALB DNS.

Service endpoints

Region NameRegionEndpointRoute 53 Hosted Zone ID (Application Load Balancers, Classic Load Balancers)Route 53 Hosted Zone ID (Network Load Balancers)
US East (N. Virginia)us-east-1elasticloadbalancing.us-east-1.amazonaws.com
elasticloadbalancing-fips.us-east-1.amazonaws.com
Z35SXDOTRQ7X7KZ26RNL4JYFTOTI
https://docs.aws.amazon.com/general/latest/gr/elb.html
HOSTEDZONE_ID_ALB="Z35SXDOTRQ7X7K"

# Get hosted zone ID of awsexample.com
HOSTEDZONE_ID_R53=$(aws route53 list-hosted-zones-by-name \
    --dns-name awsexample.com \
    --query "HostedZones[0].Id" \
    --output text)

# Get domain name of ALB
ALB_DNS=$(aws elbv2 describe-load-balancers \
    --query "LoadBalancers[?LoadBalancerName=='AssociatingSubnets-alb'].DNSName" \
    --output text)

# Create record for ALB
aws route53 change-resource-record-sets \
    --hosted-zone-id $HOSTEDZONE_ID_R53 \
    --change-batch '{
      "Comment": "Creating ALB alias for api.awsexample.com",
      "Changes": [
        {
          "Action": "UPSERT",
          "ResourceRecordSet": {
            "Name": "api.awsexample.com",
            "Type": "A",
            "AliasTarget": {
              "HostedZoneId": "'"$HOSTEDZONE_ID_ALB"'", 
              "DNSName": "'"$ALB_DNS"'",
              "EvaluateTargetHealth": false
            }
          }
        }
      ]
    }'

User‘s Procedure

Employees will associate the AWS Client VPN endpoint through a Lambda function.
The specific steps are as follows:

  1. Access the following URL from a browser:
    URL: https://api.awsexample.com
  2. Log in using your user ID and password registered in Active Directory.

By following the above steps, the AWS Client VPN endpoint will be associated.
If this is your first time logging in, you will need to register an MFA device since MFA registration is enabled by default in IAM Identity Center. For first-time login, please follow the steps below:

  1. Install Google Authenticator (MFA authentication app) on your smartphone.
    [Google Play / App Store]
  2. Proceed with the Register MFA Device process by selecting the authentication app:
    • Display the QR code.
    • Launch Google Authenticator on your smartphone.
    • Tap the "+" button in the bottom-right corner of the app and scan the QR code.
    • Enter the 6-digit authentication code displayed under the AWS SSO section in the app.

Employees are informed to follow the above steps when using AWS Client VPN.
This minimizes the time required for association and helps reduce costs.
We are planning to implement a mechanism to automatically disassociate the connection, which will be introduced in the next article.

Copied title and URL