AWS Builder Center

Filtering AWS Identity Store Users by Status: A Complete Guide

AWS IAM Identity Center now supports user status filtering through the Identity Store API. This comprehensive guide walks you through building a Python automation script to list, filter, and audit users by their ENABLED or DISABLED status across single or multiple AWS accounts. Perfect for security audits, compliance reporting, and large-scale account cleanup operations.

Introduction

Managing user accounts at scale in AWS IAM Identity Center can be challenging, especially when dealing with large-scale account cleanup operations. Organizations often face scenarios where thousands of user accounts need to be audited, and determining which accounts are active versus disabled becomes critical for security and compliance.
The Challenge:
Consider a common scenario: An organization discovers a large number of user accounts that were inadvertently created in their IAM Identity Center. These accounts exist in the root billing account where Identity Center is configured, and the team needs to:
  1. Audit all user accounts to understand their current status
  2. Identify which accounts are enabled versus disabled
  3. Clean up inactive or unnecessary accounts safely
  4. Proceed carefully given the scale and potential impact on operations
Previously, administrators faced a significant limitation: while the Identity Store API provided user information, there was no way to determine if an account was enabled or disabled. This made large-scale audits and cleanup operations extremely difficult and error-prone.
The Solution:
AWS IAM Identity Center recently added a highly requested feature: the UserStatus field in the Identity Store API. This field now appears in both ListUsers and DescribeUser API responses, making it possible to programmatically audit user account status across your organization.
In this guide, we'll walk through building a Python automation script that:
  • Lists all users with their current status (ENABLED/DISABLED)
  • Filters users based on their status for targeted audits
  • Scales to handle large user populations efficiently
  • Works across multiple AWS accounts in an organization structure
This solution enables the systematic audit and cleanup operations that were previously impossible, helping organizations maintain security hygiene at scale.

What's New in the Identity Store API?

The Identity Store API now returns a UserStatus field with these possible values:
  • ENABLED: User can authenticate and access resources
  • DISABLED: User account is blocked from authentication
  • UNKNOWN: Status information unavailable (legacy users)

Important Limitation

While the API returns status information, it does not support server-side filtering by status. This means you need to:
  1. Call ListUsers to retrieve all users
  2. Filter the results client-side based on the UserStatus field
This approach is perfectly acceptable for most use cases, as Identity Store user lists are typically manageable in size.

The Solution: A Python Automation Script

Let's build a script that handles both single-account and multi-account scenarios.

Design Goals

  • Auto-discovery: Automatically find the Identity Store ID
  • Flexible filtering: Support filtering by ENABLED/DISABLED status
  • Organization support: Scan multiple accounts in AWS Organizations
  • Simple interface: Minimal command-line arguments

Process Overview

Architecture diagram showing the workflow of the Identity Store user status script: Script calls SSO Admin API to get Identity Store ID, then calls Identity Store API to list users with status, applies client-side filtering, and displays results. The filtering step is highlighted in AWS orange.
Key Components:
  1. Auto-Discovery: Finds Identity Store ID via SSO Admin API
  2. Pagination Handler: Processes large user lists efficiently
  3. Client-Side Filter: Filters by UserStatus after retrieval
  4. Cross-Account Access: Uses STS AssumeRole for organization scanning

Implementation

Step 1: Core Functions

Let's start with the essential building blocks:
1
2
3
4
5
6
7
8
import boto3
import sys

def get_identity_store_id(session):
"""Auto-discover Identity Store ID from SSO instance."""
sso = session.client('sso-admin')
instances = sso.list_instances()['Instances']
return instances[0]['IdentityStoreId'] if instances else None
This function eliminates the need for users to manually provide the Identity Store ID—a common pain point in automation scripts.

Step 2: User Listing with Pagination

The Identity Store API uses pagination for large user lists. Here's how we handle it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def list_users(session, identity_store_id, status_filter=None):
"""List users with optional status filtering."""
client = session.client('identitystore')
paginator = client.get_paginator('list_users')

users = []
for page in paginator.paginate(IdentityStoreId=identity_store_id):
for user in page['Users']:
status = user.get('UserStatus', 'UNKNOWN')

# Client-side filtering
if not status_filter or status == status_filter:
users.append({
'UserName': user['UserName'],
'Status': status,
'DisplayName': user.get('DisplayName', 'N/A')
})

return users
Notice the client-side filtering logic—we check each user's status after retrieval since the API doesn't support filtering parameters.

Step 3: Organization Support

For enterprises managing multiple AWS accounts, we need cross-account access:
1
2
3
4
5
6
7
8
9
10
def get_org_accounts():
"""Get all active accounts in the organization."""
org = boto3.client('organizations')
accounts = []
paginator = org.get_paginator('list_accounts')

for page in paginator.paginate():
accounts.extend([a for a in page['Accounts'] if a['Status'] == 'ACTIVE'])

return accounts
The script assumes the OrganizationAccountAccessRole exists in member accounts—a standard role created by AWS Organizations.

Step 4: Main Logic

Bringing it all together with a clean command-line interface that handles both single-account and organization modes. The complete implementation is provided at the end of this guide.

Usage Examples

Single Account Scenarios

List all users with their status:
1
python list_users_by_status.py
Output:
1
2
3
4
5
6
7
8
Found 7 user(s):
john.doe@example.com ENABLED John Doe
jane.smith@example.com ENABLED Jane Smith
bob.wilson@example.com ENABLED Bob Wilson
alice.chen@example.com ENABLED Alice Chen
mike.johnson@example.com ENABLED Mike Johnson
sarah.davis@example.com ENABLED Sarah Davis
former.employee@example.com DISABLED Former Employee
Find only disabled users:
1
python list_users_by_status.py DISABLED
Output:
1
2
Found 1 user(s):
former.employee@example.com DISABLED Former Employee

Organization-Wide Scenarios

Scan all accounts for disabled users:
1
python list_users_by_status.py DISABLED --org
Output:
1
2
3
4
5
6
7
Scanning 5 accounts in organization...

Account: Production (123456789012)
old.contractor@example.com DISABLED Former Contractor

Account: Development (234567890123)
test.user@example.com DISABLED Test Account
This is particularly useful for compliance audits where you need to verify that disabled accounts exist across your entire organization.

Real-World Use Cases

1. Security Audits

Quickly identify all disabled accounts across your organization to ensure former employees or contractors no longer have access:
1
python list_users_by_status.py DISABLED --org > disabled_users_audit.txt

2. Compliance Reporting

Generate regular reports showing the status of all user accounts for compliance teams:
1
2
# Weekly report
python list_users_by_status.py --org > weekly_user_status_$(date +%Y%m%d).txt

3. Account Cleanup

Find disabled accounts that can be safely deleted after a retention period:
1
python list_users_by_status.py DISABLED > accounts_to_review.txt

4. Onboarding Verification

Verify that newly created accounts are properly enabled:
1
python list_users_by_status.py ENABLED | grep "new.hire@example.com"

Best Practices

1. Use Virtual Environments

Always isolate your Python dependencies to avoid conflicts with system packages. This follows Python best practices and prevents dependency issues.
1
2
3
python3 -m venv .venv
source .venv/bin/activate
pip install boto3

2. Secure Credential Management

Never hardcode credentials in your scripts. AWS recommends the following credential chain (in order of precedence):
  1. IAM roles (recommended for EC2/Lambda/ECS execution)
  2. AWS CLI configuration (~/.aws/credentials and ~/.aws/config)
  3. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
  4. AWS SSO login for temporary credentials
For more information, see AWS Security Best Practices .

3. Implement Proper Error Handling

For production use, implement comprehensive error handling for common AWS exceptions:
  • AccessDeniedException - Verify IAM permissions
  • ResourceNotFoundException - Check Identity Store ID
  • ThrottlingException - Implement exponential backoff retry logic
The complete script at the end of this guide includes proper error handling.

4. Handle API Throttling

For large organizations, implement exponential backoff to handle API throttling gracefully. AWS API Gateway and services use the token bucket algorithm for throttling. When you exceed limits, you'll receive 429 Too Many Requests or ThrottlingException errors.
The complete script includes retry logic with exponential backoff. For more information, see AWS Well-Architected Framework - Throttle Requests .

5. Apply Least Privilege Permissions

Grant only the minimum permissions required for the script to function. Avoid using overly permissive policies like AdministratorAccess.
Required permissions for single-account mode:
  • sso:ListInstances
  • identitystore:ListUsers
Additional permissions for organization mode:
  • organizations:ListAccounts
  • sts:AssumeRole
This follows the AWS IAM least privilege principle . See the IAM Permissions section in the USER_GUIDE.md for complete policy examples.

Troubleshooting

"No Identity Store found"

Cause: IAM Identity Center is not enabled in the account.
Solution: Enable IAM Identity Center in the AWS Console under "IAM Identity Center" service.

"Access denied" in organization mode

Cause: Missing OrganizationAccountAccessRole or insufficient permissions.
Solution:
  1. Verify the role exists in member accounts
  2. Check your IAM user/role has sts:AssumeRole permission
  3. Ensure the trust policy allows your account to assume the role

"Module not found: boto3"

Cause: boto3 not installed or virtual environment not activated.
Solution:
1
2
source .venv/bin/activate
pip install boto3

Performance Considerations

Pagination Efficiency

The script uses boto3's built-in pagination, which automatically handles the NextToken parameter for retrieving subsequent pages. According to AWS documentation:
  • Identity Store ListUsers API: Returns paginated results with a default page size
  • MaxResults parameter: Can be specified to control page size (check API limits)
  • Automatic handling: boto3 paginators abstract away the complexity of token management
The pagination is efficient and handles user lists of any size without loading all results into memory at once.

API Throttling Limits

Be aware of AWS Organizations and Identity Store API throttling limits:
  • AWS Organizations APIs: Have specific throttling limits for account management operations (see AWS Organizations Quotas )
  • Identity Store APIs: Have throttling limits that can be increased by opening a support case (see IAM Identity Center Quotas )
  • Token bucket algorithm: AWS uses this algorithm for API throttling - when limits are exceeded, you'll receive ThrottlingException errors
For large-scale operations, implement exponential backoff retry logic (shown in Best Practices section).

Organization Scanning Time

Scanning time depends on:
  • Number of accounts in your organization
  • Number of users per account
  • API throttling limits and retry delays
  • Network latency to AWS endpoints
Typical performance: For an organization with 10 accounts and 100 users each, expect approximately 30-60 seconds total runtime, assuming no throttling.

Optimization Strategies

  1. Parallel Processing: For very large organizations (50+ accounts), consider using Python's concurrent.futures to scan multiple accounts in parallel, while respecting API rate limits
  2. Caching: Cache Identity Store IDs to avoid repeated ListInstances API calls across multiple script executions
  3. Targeted Scanning: If you know which accounts have IAM Identity Center enabled, maintain a list and skip accounts without it
  4. Batch Operations: Group operations where possible to minimize API calls
  5. Monitor Throttling: Track ThrottlingException occurrences and adjust concurrency or add delays accordingly

Future Enhancements

While this script covers the essentials, here are some ideas for extension:
  1. JSON Output: Add --json flag for machine-readable output
  2. CSV Export: Generate CSV files for spreadsheet analysis
  3. Email Notifications: Send alerts when disabled accounts are found
  4. CloudWatch Integration: Push metrics to CloudWatch for monitoring
  5. Lambda Deployment: Run as a scheduled Lambda function for automated audits

Conclusion

The addition of UserStatus to the Identity Store API is a welcome improvement for AWS administrators. While server-side filtering isn't available yet, client-side filtering is straightforward and performant for typical use cases.
This script provides a solid foundation for user status management across single and multi-account environments. Whether you're conducting security audits, generating compliance reports, or simply keeping track of your user base, this tool streamlines the process.

Complete Script

Save this as list_users_by_status.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/usr/bin/env python3
"""List AWS Identity Store users by status across organization accounts."""

import boto3
import sys

def get_org_accounts():
"""Get all accounts in the organization."""
org = boto3.client('organizations')
accounts = []
paginator = org.get_paginator('list_accounts')
for page in paginator.paginate():
accounts.extend([a for a in page['Accounts'] if a['Status'] == 'ACTIVE'])
return accounts

def get_identity_store_id(session):
"""Get Identity Store ID from SSO instance."""
sso = session.client('sso-admin')
instances = sso.list_instances()['Instances']
return instances[0]['IdentityStoreId'] if instances else None

def list_users(session, identity_store_id, status_filter=None):
"""List users from Identity Store."""
client = session.client('identitystore')
paginator = client.get_paginator('list_users')

users = []
for page in paginator.paginate(IdentityStoreId=identity_store_id):
for user in page['Users']:
status = user.get('UserStatus', 'UNKNOWN')
if not status_filter or status == status_filter:
users.append({
'UserName': user['UserName'],
'Status': status,
'DisplayName': user.get('DisplayName', 'N/A')
})
return users

def main():
status_filter = sys.argv[1] if len(sys.argv) > 1 and sys.argv[1] in ['ENABLED', 'DISABLED'] else None
org_mode = '--org' in sys.argv

if org_mode:
accounts = get_org_accounts()
print(f"Scanning {len(accounts)} accounts in organization...\n")

for account in accounts:
try:
sts = boto3.client('sts')
role_arn = f"arn:aws:iam::{account['Id']}:role/OrganizationAccountAccessRole"
creds = sts.assume_role(RoleArn=role_arn, RoleSessionName='UserStatusCheck')['Credentials']

session = boto3.Session(
aws_access_key_id=creds['AccessKeyId'],
aws_secret_access_key=creds['SecretAccessKey'],
aws_session_token=creds['SessionToken']
)

identity_store_id = get_identity_store_id(session)
if not identity_store_id:
continue

users = list_users(session, identity_store_id, status_filter)
if users:
print(f"Account: {account['Name']} ({account['Id']})")
for user in users:
print(f" {user['UserName']:<30} {user['Status']:<10} {user['DisplayName']}")
print()
except Exception as e:
print(f" Skipped {account['Name']}: {str(e)}\n")
else:
session = boto3.Session()
identity_store_id = get_identity_store_id(session)

if not identity_store_id:
print("No Identity Store found")
sys.exit(1)

users = list_users(session, identity_store_id, status_filter)
print(f"Found {len(users)} user(s):")
for user in users:
print(f" {user['UserName']:<30} {user['Status']:<10} {user['DisplayName']}")

if __name__ == '__main__':
main()

Additional Resources


Questions or feedback? Reach out to your AWS account team or AWS Support for assistance.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article