OIDC External Identity Source for AWS IAM Identity Center
Overview
AWS IAM Identity Center (formerly AWS SSO) provides centralized access management for AWS accounts and applications. While it natively supports SAML 2.0 for external identity providers, many organizations prefer OIDC-based authentication through providers like Amazon Cognito. This post demonstrates how to use Cloudflare Access as a SAML bridge between Amazon Cognito and AWS IAM Identity Center with automatic just-in-time (JIT) user provisioning.
The Challenge
AWS IAM Identity Center only accepts SAML 2.0 for external identity providers. However, you might want to use an OIDC provider like Amazon Cognito for several reasons:
- Unified Identity: Use Cognito as a central user directory across your applications
- Social Login Federation: Federate Cognito with Google, Facebook, or enterprise OIDC providers
- Flexible Authentication: Leverage Cognito's MFA, adaptive authentication, and custom flows
- Zero Trust Integration: Combine with Cloudflare Access policies for enhanced security
The solution is to use Cloudflare Access as a SAML Identity Provider that bridges OIDC tokens from Cognito to SAML assertions for AWS IAM Identity Center, while a Pre-Token Lambda automatically provisions users in AWS Identity Store.
Architecture Overview
flowchart LR
subgraph User["User"]
CLI["AWS CLI"]
Console["AWS Console"]
end
subgraph IDC["IAM Identity Center"]
Portal["Access Portal"]
PS["Permission Sets"]
end
subgraph CF["Cloudflare Access"]
SAML["SAML IdP"]
ZT["Zero Trust<br/>Policies"]
end
subgraph Auth["Amazon Cognito"]
UP["User Pool"]
PTL["Pre-Token<br/>Lambda"]
ExtIdP["External OIDC<br/>(Optional)"]
end
subgraph Store["Identity Store"]
IS["AWS Identity<br/>Store"]
end
CLI --> Portal
Console --> Portal
Portal -->|SAML Request| SAML
SAML --> ZT
ZT -->|OIDC| UP
UP --> ExtIdP
UP --> PTL
PTL -->|JIT Sync| IS
SAML -->|SAML Response| Portal
Portal --> PS
Authentication Flow
- User accesses AWS access portal or initiates
aws sso login - AWS IAM Identity Center redirects to Cloudflare Access (SAML IdP)
- Cloudflare Access authenticates user via Amazon Cognito (OIDC)
- Cognito's Pre-Token Lambda creates user in Identity Store if not exists (JIT provisioning)
- Cognito returns tokens with
identity_store_user_idclaim - Cloudflare sends SAML assertion back to AWS IAM Identity Center
- User gains access to AWS resources based on permission sets
Solution Components
The solution consists of three main components:
1. Amazon Cognito User Pool
Cognito serves as the OIDC provider and user directory:
- User Authentication: Native authentication or federation with external OIDC providers
- OAuth 2.0/OIDC: Provides standard OIDC endpoints for Cloudflare Access
- Pre-Token Lambda Trigger: Invokes JIT provisioning before token generation
2. Cloudflare Access (SAML Bridge)
Cloudflare Access acts as a SAML Identity Provider:
- SAML IdP: Generates SAML assertions for AWS IAM Identity Center
- OIDC Authentication: Authenticates users via Cognito's OIDC endpoints
- Zero Trust Policies: Apply access controls before allowing AWS access
3. Pre-Token Generation Lambda
A Lambda function triggered by Cognito before token generation:
- JIT Provisioning: Creates users in AWS Identity Store on first login
- Idempotent: Handles race conditions with conflict detection
- Claims Injection: Adds
identity_store_user_idto tokens
Implementation
The complete solution is available as an AWS CDK project on GitHub.
Project Structure
1cloudflare-access-for-aws-idc/
2├── bin/
3│ └── app.ts # CDK app entry point
4├── lib/
5│ └── cognito-cloudflare-stack.ts # Main CDK stack
6├── lambda/
7│ └── pre-token-generation/
8│ └── index.ts # JIT user sync Lambda
9└── test/
10 ├── cognito-cloudflare-stack.test.ts
11 └── lambda/
12 └── pre-token-generation.test.ts
CDK Stack Highlights
The CDK stack creates the following resources:
1export interface CognitoCloudflareStackProps extends cdk.StackProps {
2 identityStoreId: string; // AWS Identity Store ID
3 cloudflareTeamName: string; // Cloudflare Access team name
4 externalOidcProvider?: { // Optional external OIDC federation
5 providerName: string;
6 clientId: string;
7 clientSecret: string;
8 issuerUrl: string;
9 };
10}
Key components created:
- Cognito User Pool with secure password policies and email-based sign-in
- Pre-Token Generation Lambda with Identity Store permissions
- User Pool Client configured for Cloudflare Access callback
- CloudWatch Dashboard and alarms for operational monitoring
- Secrets Manager secret for client credentials
Pre-Token Generation Lambda
The Lambda function synchronizes users to AWS Identity Store using the Cognito Pre Token Generation V2 trigger:
1async function syncUserToIdentityStore(
2 email: string,
3 givenName: string,
4 familyName: string,
5 displayName: string
6): Promise<string> {
7 // Check if user exists in Identity Store
8 let identityStoreUserId = await findUserInIdentityStore(email);
9
10 // Create user if not exists
11 if (!identityStoreUserId) {
12 try {
13 identityStoreUserId = await createUserInIdentityStore(
14 email, givenName, familyName, displayName
15 );
16 } catch (error) {
17 // Handle race condition: another request created the user
18 if (error instanceof ConflictException) {
19 identityStoreUserId = await findUserInIdentityStore(email);
20 } else {
21 throw error;
22 }
23 }
24 }
25
26 return identityStoreUserId;
27}
The Lambda adds the Identity Store user ID to both ID and access tokens:
1event.response.claimsAndScopeOverrideDetails = {
2 idTokenGeneration: {
3 claimsToAddOrOverride: {
4 identity_store_user_id: identityStoreUserId,
5 },
6 },
7 accessTokenGeneration: {
8 claimsToAddOrOverride: {
9 identity_store_user_id: identityStoreUserId,
10 },
11 },
12};
Smart Name Derivation
When user attributes lack name information, the Lambda derives names from the email address:
1// If both names are empty, derive from email prefix
2if (!givenName && !familyName) {
3 const emailPrefix = email.split('@')[0];
4 const nameParts = emailPrefix.split(/[._-]/);
5
6 if (nameParts.length >= 2) {
7 // john.doe@example.com -> Given: John, Family: Doe
8 givenName = capitalize(nameParts[0]);
9 familyName = nameParts.slice(1).map(capitalize).join(' ');
10 } else {
11 // johndoe@example.com -> Given: Johndoe, Family: User
12 givenName = capitalize(emailPrefix);
13 familyName = 'User';
14 }
15}
Deployment
Prerequisites
- AWS Account with IAM Identity Center enabled
- Cloudflare account with Zero Trust (Access)
- Node.js 18+ and AWS CDK CLI installed
Step 1: Find Your Identity Store ID
1aws sso-admin list-instances \
2 --query 'Instances[0].IdentityStoreId' \
3 --output text
Step 2: Deploy the CDK Stack
1# Clone the repository
2git clone https://github.com/zxkane/cloudflare-access-for-aws-idc.git
3cd cloudflare-access-for-aws-idc
4
5# Install dependencies
6npm install
7
8# Deploy
9npx cdk deploy \
10 --context identityStoreId=<your-identity-store-id> \
11 --context cloudflareTeamName=<your-team-name>
Stack Outputs
Note these outputs for Cloudflare configuration:
| Output | Description |
|---|---|
CognitoIssuerUrl | OIDC Issuer URL |
AuthorizationEndpoint | OAuth authorization endpoint |
TokenEndpoint | OAuth token endpoint |
JwksUri | JSON Web Key Set URI |
UserPoolClientId | Cognito client ID |
ClientSecretArn | ARN of client secret in Secrets Manager |
Step 3: Configure Cloudflare Access
Add OIDC Identity Provider
In Cloudflare Zero Trust dashboard:
- Go to Settings > Authentication > Login methods
- Click Add new > OpenID Connect
- Configure:
- Name: Amazon Cognito
- App ID: Use
UserPoolClientIdfrom stack outputs - Client Secret: Retrieve from Secrets Manager using
ClientSecretArn - Auth URL: Use
AuthorizationEndpointfrom stack outputs - Token URL: Use
TokenEndpointfrom stack outputs - Certificate URL: Use
JwksUrifrom stack outputs - Scopes:
openid email profile
- Save the configuration
Create SAML Application for AWS
- Go to Access > Applications > Add an application
- Select SaaS > AWS
- Configure SAML settings:
- Entity ID:
urn:amazon:webservices - ACS URL: Get from AWS IAM Identity Center external IdP settings
- Name ID Format: Email
- Entity ID:
- Assign access policies
- Download the SAML metadata
Step 4: Configure AWS IAM Identity Center
- Open AWS IAM Identity Center console
- Go to Settings → Identity source → Actions → Change identity source
- Select External identity provider
- Upload Cloudflare's SAML metadata or configure manually:
- IdP sign-in URL: From Cloudflare application settings
- IdP issuer URL: From Cloudflare application settings
- IdP certificate: Download from Cloudflare
- Complete the configuration
Step 5: Create Permission Sets and Assignments
After users authenticate for the first time, they are automatically provisioned in Identity Store. You can then:
- Create Permission Sets with appropriate IAM policies
- Assign users to AWS accounts with permission sets
Using AWS CLI with SSO
Once configured, users can authenticate via the CLI:
1# Configure SSO profile
2aws configure sso
3# Enter: SSO start URL, SSO Region, account, role
4
5# Login
6aws sso login --profile my-sso-profile
7
8# Use AWS CLI
9aws s3 ls --profile my-sso-profile
The login flow opens a browser, redirects through Cloudflare Access and Cognito for authentication, and returns credentials to the CLI.
Optional: External OIDC Federation
To federate Cognito with an external OIDC provider (Google, Okta, Auth0, etc.):
1npx cdk deploy \
2 --context identityStoreId=<your-identity-store-id> \
3 --context cloudflareTeamName=<your-team-name> \
4 --context externalOidcProviderName=Google \
5 --context externalOidcClientId=<google-client-id> \
6 --context externalOidcClientSecret=<google-client-secret> \
7 --context externalOidcIssuerUrl=https://accounts.google.com
This enables "Login with Google" (or any OIDC provider) for AWS Console and CLI access through the entire chain: External IdP → Cognito → Cloudflare → IAM Identity Center.
Monitoring and Troubleshooting
CloudWatch Dashboard
The stack creates a dashboard with:
- Lambda invocation and error metrics
- User sync success/failure rates
- Conflict and skip metrics
- Duration approaching timeout alerts
Alarms
| Alarm | Threshold | Description |
|---|---|---|
| Lambda Errors | > 0 in 5 min | Any Lambda execution errors |
| Lambda Throttles | > 0 in 5 min | Lambda throttling events |
| Sync Failures | > 5 in 5 min | Identity Store sync failures |
| Duration | p99 > 25s | Approaching 30s timeout |
Common Issues
"Looks like this code isn't right"
This usually means the user's UserName in Identity Store doesn't match the SAML NameID (email). Delete the manually-created user in Identity Center and let the Lambda recreate them on next login.
"User not found in Identity Store"
- Verify Pre-Token Lambda has
identitystore:CreateUserpermission - Check CloudWatch Logs for sync errors
- Verify the Identity Store ID is correct
Lambda Times Out
The Lambda has a 30-second timeout. If Identity Store API calls are slow:
- Check AWS service health
- Review CloudWatch logs for specific errors
Security Considerations
- Secrets Management: Client secrets stored in AWS Secrets Manager
- Least Privilege IAM: Lambda only has
GetUserIdandCreateUserpermissions - No Self-Signup: Cognito User Pool configured for admin-only user creation
- Secure OAuth: Only authorization code flow enabled (no implicit grant)
- Zero Trust: Cloudflare Access policies provide additional security layer
- X-Ray Tracing: Full observability for debugging and auditing
Conclusion
This solution enables OIDC-based authentication for AWS IAM Identity Center by leveraging Cloudflare Access as a SAML bridge. Key benefits include:
- Flexible Identity: Use Amazon Cognito as your identity provider with optional external OIDC federation
- Zero Trust Security: Apply Cloudflare Access policies before AWS access
- Automatic Provisioning: JIT user creation eliminates manual user management
- Serverless Architecture: No infrastructure to manage beyond the CDK stack
The complete source code is available on GitHub.
Resources
- AWS IAM Identity Center Documentation
- Amazon Cognito Documentation
- Cloudflare Access Documentation
- AWS CDK Documentation