Enterprise IAM for Multi-Account AWS
Design enterprise-grade IAM with SCPs, Cognito, and cross-account access.
Introduction
30 Second Summary
Every company has locked doors, keycards, and rules about who can access what. In cloud computing, those same security decisions happen through identity and access management policies.
In this project, you will design and implement a complete enterprise IAM security architecture for a fictional company called "TechCorp." You'll set up a multi-account AWS environment with fine-grained access controls, cross-account permissions, and user authentication integrated with row-level database security.
What You'll Build
You'll build an enterprise-grade IAM security architecture across multiple AWS accounts, covering Organizations, SCPs, IAM conditions, resource-based policies, and Cognito identity federation.
By the end of this project, you'll have:
- A multi-account AWS Organization with Organizational Units governed by blocklist and allowlist Service Control Policies.
- IAM policies with fine-grained conditions restricting S3 access by source IP, region, resource tags, and MFA status, plus a cross-account resource-based policy demonstrating when to use resource policies vs. IAM roles.
- A working Cognito User Pool connected to an Identity Pool that grants authenticated users row-level access to their own DynamoDB items.
- Secret Mission: Implement a permissions boundary that limits what a delegated admin can grant, then test the policy evaluation logic by combining SCPs, permissions boundaries, and identity policies.
Are there any prerequisites?
You'll need an AWS account with admin access and basic familiarity with IAM concepts like users, roles, policies, and groups. If you've created IAM users or attached policies before, you're ready to go.
Set Up AWS Organizations and Create a Member Account
Every enterprise AWS environment starts with a multi-account structure. Separating workloads into different accounts creates natural security boundaries and makes it easier to apply policies to groups of accounts.
In this step, you will create an AWS Organization from your existing account, build two Organizational Units (Production and Sandbox), and provision a new member account for TechCorp's sandbox environment.
In this step, get ready to:
- Create an AWS Organization with all features enabled.
- Create Production and Sandbox Organizational Units under the root.
- Create a new member account and move it into the Sandbox OU.
Create an AWS Organization
An AWS Organization lets you centrally manage multiple AWS accounts under one umbrella. Your current account becomes the management account, which controls billing, policies, and governance for all member accounts.
- In the AWS Management Console, search for and open Organizations.
- On the introduction page, choose Create an organization.
- In the confirmation dialog box, choose Create an organization.
The organization is created with all features enabled by default. You should see the AWS accounts page with your management account listed under the Root.
What is a management account?
Your current AWS account automatically becomes the management account when you create an Organization. This account has special privileges: it can create member accounts, apply policies, and manage the entire organizational structure.
Service Control Policies (SCPs) you attach later will affect member accounts, but never the management account itself.
What about email verification?
AWS may send a verification email to the address associated with your management account. You can create new accounts without verifying, but you will need to verify within 24 hours if you want to invite existing accounts later.
Create Production and Sandbox Organizational Units
Organizational Units (OUs) let you group accounts and apply policies to an entire group at once. TechCorp needs a Production OU for live workloads and a Sandbox OU for developer experimentation.
- On the AWS accounts page in the Organizations console, select the check box next to the Root container.
- Choose the Actions dropdown, then under Organizational unit, choose Create new.
- For Organizational unit name, enter Production and choose Create organizational unit.
Now create the Sandbox OU using the same process.
- Make sure the check box next to Root is still selected (not the Production OU).
- Choose Actions, then under Organizational unit, choose Create new.
- For Organizational unit name, enter Sandbox and choose Create organizational unit.
You should now see both Production and Sandbox listed as children of the Root on the AWS accounts page.
Why separate Production and Sandbox OUs?
Each OU can have different policies attached. The Production OU will eventually have strict controls, while the Sandbox OU gives developers room to experiment with a more limited but less restrictive set of approved services.
Policies applied at the OU level automatically apply to every account inside that OU. This is much simpler than managing policies account by account.
Don't see your new OUs?
Make sure you are on the AWS accounts page in the Organizations console. If you see a flat list of accounts with no hierarchy, toggle to Hierarchy view at the top of the Organization section.
If you accidentally created an OU inside Production instead of under Root, you can delete it and try again. Make sure the Root check box is selected before choosing Actions.
Help me figure out why my OUs aren't showing up correctly.
Create a member account and move it to the Sandbox OU
TechCorp's developers need their own account for experimentation. Creating a member account inside your Organization means it automatically inherits the Organization's governance. You will need a unique email address that is not already associated with an existing AWS account.
- On the AWS accounts page, choose Add an AWS account.
- Select Create an AWS account (this is the default option).
- For AWS account name, enter TechCorp-Sandbox.
- For Email address of the account's owner, enter a valid email address that is not already associated with another AWS account.
- Leave the IAM role name as the default (OrganizationAccountAccessRole).
- Choose Create AWS account.
Use an email alias for the member account
Many email providers support "+" aliasing. For example, if your email is yourname@gmail.com, you can use yourname+sandbox@gmail.com as a unique address that still delivers to your inbox.
Account creation takes a few moments. Wait for the status to show as Active before proceeding. You may need to refresh the page.
Now move the new account into the Sandbox OU.
- On the AWS accounts page, select the check box next to your new TechCorp-Sandbox account (not its name).
- Choose Actions, then under AWS account, choose Move.
- On the Move page, select the radio button next to Sandbox, then choose Move AWS account.
Your TechCorp-Sandbox account now lives inside the Sandbox OU. Expand the Sandbox OU in the hierarchy view to confirm.
What is OrganizationAccountAccessRole?
When you create a member account, AWS automatically creates an IAM role called OrganizationAccountAccessRole in that account. This role allows users in the management account to assume it and gain full administrative access to the member account.
You will use this role later when testing cross-account access patterns.
Account still showing as pending?
Account creation can take up to a few minutes. Refresh the AWS accounts page. If the account stays in a pending state for more than 10 minutes, check that the email address you provided is valid and not already linked to another AWS account.
Help me troubleshoot my member account creation in AWS Organizations.
Your Organization structure is in place. Next up, you will write Service Control Policies to enforce blocklist and allowlist strategies across these OUs.
Implement SCP Blocklist and Allowlist Strategies
Your Organization is set up with OUs and a member account. Now you need to enforce security guardrails that prevent dangerous actions and limit what services the sandbox account can use.
In this step, you'll implement two SCP strategies that enterprises use in production. A blocklist adds explicit denials on top of full access, while an allowlist restricts access to only approved services.
In this step, get ready to:
- Create a blocklist SCP that denies high-risk actions across all member accounts.
- Create an allowlist SCP that permits only approved services for the Sandbox OU.
- Verify that SCPs enforce boundaries on member accounts while leaving the management account unaffected.
Create and attach a blocklist SCP to the Organization root
A blocklist strategy keeps the default FullAWSAccess SCP in place (allowing everything) and layers explicit Deny statements on top. This blocks specific dangerous actions while leaving all other permissions available.
- In the AWS Management Console, navigate to the AWS Organizations service.
- In the left navigation pane, select Policies, then choose Service control policies.
- Choose Create policy.
- Enter TechCorp-Blocklist-SCP in the Policy name field.
- In the Policy description field, enter Denies leaving the organization and disabling CloudTrail.
- In the JSON editor on the left side of the page, delete the default policy content and paste the following JSON:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyLeavingOrganization",
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
},
{
"Sid": "DenyDisablingCloudTrail",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail"
],
"Resource": "*"
}
]
}
What does this policy do?
This SCP has two Deny statements. The first prevents any member account from removing itself from the Organization. The second prevents anyone from stopping or deleting CloudTrail logging, which would hide their activity.
Because this is a blocklist, you keep FullAWSAccess in place. The Deny statements override the Allow for these specific actions only.
- Choose Create policy to save the SCP.
Now attach this blocklist SCP to the Organization root so it applies to all member accounts.
- On the Service control policies page, choose the name TechCorp-Blocklist-SCP to open the policy.
- Select the Targets tab, then choose Attach.
- Choose the radio button next to Root (the top-level entity in your Organization).
- Choose Attach policy.
The Targets tab now shows Root as an attached target. The blocklist SCP is active alongside the default FullAWSAccess SCP.
Why attach to the root?
Attaching a blocklist SCP to the root means it applies to every OU and account in the Organization. This creates a universal security baseline. High-risk actions like leaving the Organization or disabling audit logging should be blocked everywhere, not just in certain OUs.
Don't see your new SCP in the list?
Make sure you chose Create policy at the bottom of the page after pasting the JSON. If the JSON has a syntax error (missing comma, extra bracket), the console shows a validation error at the top of the editor.
Help me troubleshoot why my SCP didn't save in AWS Organizations.
Create an allowlist SCP for the Sandbox OU
An allowlist strategy works differently from a blocklist. Instead of blocking specific actions, you explicitly list only the services you allow. Anything not on the list is implicitly denied.
- Navigate back to the Service control policies page in the left navigation pane.
- Choose Create policy.
- Enter TechCorp-Sandbox-Allowlist-SCP in the Policy name field.
- In the Policy description field, enter Allows only EC2, S3, and IAM actions for sandbox accounts.
- In the JSON editor, delete the default content and paste this policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowApprovedServicesOnly",
"Effect": "Allow",
"Action": [
"ec2:*",
"s3:*",
"iam:*"
],
"Resource": "*"
}
]
}
What does this policy do?
This SCP allows only EC2, S3, and IAM actions. Any other AWS service (Lambda, DynamoDB, RDS, etc.) is implicitly denied because there is no Allow statement covering those services.
For an allowlist to work, you must remove FullAWSAccess from the target. Otherwise FullAWSAccess still allows everything and your allowlist has no effect.
- Choose Create policy to save.
Now attach the allowlist SCP to the Sandbox OU and remove the default FullAWSAccess from that OU.
- On the Service control policies page, choose TechCorp-Sandbox-Allowlist-SCP to open the policy.
- Select the Targets tab, then choose Attach.
- Expand your Organization's OUs and choose the radio button next to Sandbox OU.
- Choose Attach policy.
Now detach FullAWSAccess from the Sandbox OU so the allowlist takes effect.
- In the left navigation, choose AWS accounts.
- Choose the name of your Sandbox OU to open its details page.
- Select the Policies tab.
- Under Service control policies, you should see both FullAWSAccess and TechCorp-Sandbox-Allowlist-SCP listed.
- Choose the radio button next to FullAWSAccess, then choose Detach.
- In the confirmation dialog box, choose Detach policy.
Important: Order matters
You must attach the replacement allowlist SCP before detaching FullAWSAccess. Every OU must have at least one SCP attached at all times. If you try to detach FullAWSAccess first, the console will block you.
The Sandbox OU now has only TechCorp-Sandbox-Allowlist-SCP attached. Member accounts in this OU can only use EC2, S3, and IAM.
Why does removing FullAWSAccess matter?
SCPs do not grant permissions. They set the maximum boundary of what is possible. For a permission to be allowed, there must be an explicit Allow statement at every level from the root through each OU in the path to the account.
By removing FullAWSAccess and replacing it with your allowlist, you ensure that only EC2, S3, and IAM can pass through the SCP boundary at the Sandbox OU level. Any other service is implicitly denied even if the account's IAM policies allow it.
Accidentally locked yourself out?
If you detached FullAWSAccess without first attaching the allowlist, you may not be able to perform actions in the member account. Remember that SCPs do not affect the management account. Sign in to the management account and re-attach FullAWSAccess to the Sandbox OU to restore access, then try again in the correct order.
Help me fix my SCP configuration after accidentally removing FullAWSAccess.
Verify SCP behavior across accounts
SCPs only affect member accounts. The management account is always exempt. You can verify this by confirming that restricted actions fail in the member account but succeed in the management account.
- Stay signed in to the management account.
- Navigate to CloudTrail in the console.
- Confirm that you can access CloudTrail settings and trails without any access denied errors.
The management account is unaffected by SCPs. This confirms the blocklist SCP only restricts member accounts.
- Now test the Sandbox account's restrictions. Switch to your member account by choosing your account name in the top-right corner, then choosing Switch role (or sign in directly to the member account if you set up a user there).
- Once in the member account, try navigating to a service outside the allowlist, such as Lambda or DynamoDB.
- Attempt to perform any action (list functions, create a table). You should see an access denied error because the Sandbox OU allowlist only permits EC2, S3, and IAM.
What should I see?
In the member account, services outside the allowlist (like Lambda or DynamoDB) return access denied messages when you try to use them. Services inside the allowlist (EC2, S3, IAM) work normally, subject to the account's own IAM policies.
In the management account, all services remain accessible. SCPs never restrict the management account regardless of where they are attached.
Quick verification without switching accounts
If you haven't set up login credentials for the member account yet, you can verify the SCP configuration visually. Navigate to AWS accounts in Organizations, choose the Sandbox OU, and confirm the Policies tab shows only TechCorp-Sandbox-Allowlist-SCP (not FullAWSAccess). Then check the Root's Policies tab shows both FullAWSAccess and TechCorp-Blocklist-SCP.
Your Organization now enforces two SCP strategies. The blocklist protects against dangerous actions organization-wide, while the allowlist restricts the sandbox to approved services only. Next up, you'll write fine-grained IAM policies with conditions to control access at an even more granular level.
Write IAM Policies with Conditions
Your Organization now has guardrails at the account level through SCPs. But SCPs only set the maximum ceiling of what member accounts can do. They don't control how individual users or roles access specific resources like S3 buckets.
IAM condition keys let you add fine-grained restrictions to policies based on request context. In this step, you'll write policies that restrict S3 access based on the requester's IP address, the AWS Region of the request, whether MFA was used, and what tags are on the target resource.
In this step, get ready to:
- Create an S3 bucket and an IAM policy restricting access by source IP and region.
- Create a second IAM policy requiring MFA for deletes and restricting access by resource tags.
- Test policy behavior by assuming a role with and without conditions met.
Create an S3 bucket and IP/Region condition policy
Before you can test access restrictions, you need an S3 bucket to target. You'll also create your first condition-based policy that denies access unless the request matches an allowed IP range and region.
- In the AWS Management Console, search for S3 in the top search bar and select the S3 service.
- Click Create bucket.
- Enter a bucket name following this format: techcorp-iam-demo-bucket- followed by a unique identifier like your initials and a number (e.g. techcorp-iam-demo-bucket-jd42). S3 bucket names must be globally unique.
- Leave all other settings at their defaults.
- Scroll down and click Create bucket.
Why a globally unique bucket name?
S3 bucket names share a global namespace across all AWS accounts. Adding a personal suffix like your initials ensures your name doesn't conflict with someone else's bucket.
Now you'll create an IAM policy that denies S3 access unless two conditions are met: the request comes from an allowed IP range, and the request targets a specific AWS Region.
- Search for IAM in the top search bar and select the IAM service.
- In the left navigation pane, click Policies.
- Click Create policy.
- Switch to the JSON tab in the policy editor.
- Delete the default content and paste the following policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyS3AccessOutsideAllowedIP",
"Effect": "Deny",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": "203.0.113.0/24"
}
}
},
{
"Sid": "DenyS3AccessOutsideAllowedRegion",
"Effect": "Deny",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}
]
}
What does this policy do?
This policy uses two Deny statements with negative condition operators. Each statement blocks S3 access when a specific condition is NOT met.
- Statement 1 uses NotIpAddress with aws:SourceIp. If the requester's IP is NOT in the 203.0.113.0/24 range, the request is denied. The 203.0.113.0/24 range is a documentation-reserved CIDR block that won't match your real IP, which makes it perfect for testing deny behavior.
- Statement 2 uses StringNotEquals with aws:RequestedRegion. If the API call targets any region other than us-east-1, the request is denied.
This policy does not grant any permissions on its own. It only restricts access by denying requests that don't meet the conditions. You'll pair it with an Allow policy on the role later.
- Click Next.
- In the Policy name field, enter TechCorp-S3-IP-Region-Policy.
- Click Create policy.
You should see a success banner confirming the policy was created.
Create an MFA and tag-based condition policy
Your first policy covers network and region restrictions. Now you'll create a second policy that enforces MFA for destructive operations and restricts read access based on resource tags.
- From the Policies page, click Create policy again.
- Switch to the JSON tab.
- Delete the default content and paste the following policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyDeleteWithoutMFA",
"Effect": "Deny",
"Action": [
"s3:DeleteObject",
"s3:DeleteBucket"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
},
{
"Sid": "DenyAccessToUntaggedResources",
"Effect": "Deny",
"Action": "s3:GetObject",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:ResourceTag/Department": "Engineering"
}
}
}
]
}
What does this policy do?
This policy adds two more layers of protection to S3 access.
- Statement 1 uses BoolIfExists with aws:MultiFactorAuthPresent. If MFA is not present in the session (or the key is missing entirely), delete operations are denied. The IfExists suffix means the deny fires even when the MFA key isn't in the request context at all.
- Statement 2 uses StringNotEquals with aws:ResourceTag/Department. If the target S3 object doesn't have a tag Department set to Engineering, the GetObject request is denied.
- Click Next.
- In the Policy name field, enter TechCorp-S3-MFA-Tag-Policy.
- Click Create policy.
You should see a success banner confirming your second policy was created.
Test the policies by assuming a role
Policies don't do anything until they're attached to an identity. You'll create an IAM role with S3 permissions plus your two condition policies, then use the IAM Policy Simulator to observe allow and deny behavior.
- In the IAM left navigation pane, click Roles.
- Click Create role.
- Under Trusted entity type, select AWS account.
- Select This account (the management account ID should be pre-filled).
- Click Next.
Why trust your own account?
By setting the trust policy to your own account, any IAM user or role in this account with sts:AssumeRole permission can switch to this test role. Since you're the admin, you already have that permission.
- On the Add permissions page, search for AmazonS3FullAccess and check the box next to it.
- Also search for and check TechCorp-S3-IP-Region-Policy.
- Also search for and check TechCorp-S3-MFA-Tag-Policy.
- Click Next.
- In the Role name field, enter TechCorp-S3-Test-Role.
- Click Create role.
Why attach AmazonS3FullAccess alongside the deny policies?
The condition-based policies only deny requests. They don't grant any permissions. The AmazonS3FullAccess managed policy provides the Allow statements for S3 actions. Your deny policies then override that allow when conditions aren't met. This is the standard pattern: allow broadly, deny selectively.
Now you'll use the IAM Policy Simulator to test how your conditions affect access decisions without making real API calls.
- Open the IAM Policy Simulator in a new tab by navigating to https://policysim.aws.amazon.com/.
- In the left panel under Users, Groups, and Roles, expand Roles and select TechCorp-S3-Test-Role.
- In the Policy Simulator section, select Amazon S3 from the service dropdown.
- Check the action ListBuckets.
- Expand Global Settings and enter 192.168.1.1 in the aws:SourceIp field. This simulates a request from an IP outside the allowed range.
- Click Run Simulation.
The result should show denied because 192.168.1.1 is not in the 203.0.113.0/24 range. The IP condition deny fired.
- Now change the aws:SourceIp value to 203.0.113.50 (an IP within the allowed range).
- Click Run Simulation again.
The result should now show allowed. The IP is inside the allowed range, and the default region matches, so neither deny statement triggers.
- Next, test the MFA condition. Select the DeleteBucket action instead.
- Keep the source IP as 203.0.113.50 so the IP condition passes.
- Expand the action row for DeleteBucket and find the aws:MultiFactorAuthPresent condition key. Set it to false.
- Click Run Simulation.
The result should show denied. Even though the IP condition passed, the MFA condition denied the delete operation because MFA was not present.
What did you just prove?
You proved that IAM condition policies layer on top of each other. Even when one condition passes (IP in range), another condition (MFA required for deletes) can still block the request. An explicit deny in any policy always wins, regardless of what other policies allow.
This is the core principle of IAM policy evaluation: an explicit Deny in any attached policy overrides all Allow statements.
Simulation showing unexpected results?
Make sure you selected the correct role (TechCorp-S3-Test-Role) in the left panel. If you left it on a different user or role, the simulation evaluates a different set of policies.
Also verify that the aws:SourceIp field under Global Settings has a value entered. Empty condition keys are ignored during simulation, which can produce unreliable results.
Help me troubleshoot my IAM Policy Simulator results for condition-based policies.
Your condition policies are working. You've proven that IP restrictions, region restrictions, and MFA requirements all enforce independently. Next, you'll explore how resource-based policies enable cross-account access without assuming a role.
Implement Cross-Account Access with Resource-Based Policies and IAM Roles
In the previous step, you wrote IAM policies with conditions that restrict who can access your S3 bucket and under what circumstances. Now you'll tackle a different challenge: letting users from a completely different AWS account access that same bucket.
There are two approaches to cross-account access in AWS. You can use a resource-based policy (like a bucket policy) that names the external principal directly. Or you can create an IAM role in your account that the external account assumes. Each approach handles the caller's original permissions differently, and understanding when to use which is a critical SAA exam topic.
In this step, get ready to:
- Add a resource-based bucket policy granting cross-account read access to your member account.
- Create a cross-account IAM role with a trust policy for role assumption.
- Compare how each approach handles the principal's original permissions.
Add a resource-based bucket policy for cross-account access
A resource-based policy is attached directly to the resource (in this case, your S3 bucket) and names which external principals can access it. The key benefit is that the calling principal retains their own account's permissions while also gaining the access the resource policy grants.
- In the AWS Management Console, navigate to the S3 service.
- Click on your techcorp-iam-demo-bucket bucket.
- Select the Permissions tab.
- Scroll down to the Bucket policy section and click Edit.
- Paste the following bucket policy into the policy editor:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CrossAccountReadAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[[MEMBER_ACCOUNT_ID=\"your member account ID\"]]:root"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::techcorp-iam-demo-bucket",
"arn:aws:s3:::techcorp-iam-demo-bucket/*"
]
}
]
}
What does this policy do?
The Principal field names the member account's root principal, meaning any IAM entity in that account can potentially access the bucket (subject to their own identity-based policies).
The policy grants two read actions: s3:ListBucket (to list objects in the bucket) and s3:GetObject (to download objects). The Resource field includes both the bucket ARN (for ListBucket) and the wildcard path (for GetObject on individual objects).
- Replace the member account ID placeholder with your actual member account's 12-digit account ID.
- Click Save changes.
Find your member account ID
You can find your member account's ID in the AWS Organizations console under AWS accounts. Click on your member account to see its 12-digit account ID.
Now test cross-account access by signing into the member account.
- Open a new private/incognito browser window.
- Sign into the AWS Console using your member account's root email and password.
- Navigate to the S3 service.
- In the S3 console, you won't see the bucket listed (it belongs to the management account). Instead, use the search bar or navigate to the bucket's URL directly.
- Try accessing the bucket by entering its ARN or using the AWS CLI from the member account context.
Why does the member account keep its own permissions?
When access is granted through a resource-based policy, the principal still works in their own account and does not have to give up their permissions to receive access. The resulting permissions are the union of the principal's own identity-based policies plus the resource-based policy's grant.
This means a user in the member account can access both the cross-account bucket and any resources their own account policies allow, at the same time.
Can't access the bucket from the member account?
Make sure the 12-digit account ID in your bucket policy matches your member account exactly. A single digit mismatch will deny access.
Remember that the Sandbox OU's allowlist SCP must include S3 actions for the member account to interact with S3 at all. Your allowlist already includes S3, so this should work.
Help me troubleshoot cross-account bucket access from my member account.
Create a cross-account IAM role for role assumption
The second approach to cross-account access uses an IAM role. Instead of the resource naming an external principal directly, you create a role in your account that the external account can assume. The critical difference is that when a user assumes a role, their original permissions are suspended.
- Return to your management account's browser window.
- Navigate to the IAM service.
- Click Roles in the left navigation pane.
- Click Create role.
- Under Trusted entity type, select Custom trust policy.
- Paste the following trust policy into the Custom trust policy editor:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[[MEMBER_ACCOUNT_ID=\"your member account ID\"]]:root"
},
"Action": "sts:AssumeRole"
}
]
}
What does this trust policy do?
The trust policy defines WHO can assume this role. By specifying the member account's root principal, you're allowing any IAM entity in that account (with appropriate permissions) to call sts:AssumeRole on this role.
This is different from the bucket policy approach. The bucket policy grants access directly to the resource. The trust policy just opens the door for the external account to assume the role. The role's permissions policy (next step) defines what the assumed role can actually do.
- Click Next.
- On the Add permissions page, search for AmazonS3ReadOnlyAccess and select it.
- Click Next.
- Enter TechCorp-CrossAccount-Role as the role name.
- Click Create role.
What happens when the member account assumes this role?
When a user in the member account assumes TechCorp-CrossAccount-Role, their original permissions are suspended entirely. Only the permissions attached to the assumed role apply.
This is the fundamental difference from the resource-based policy approach. With a resource-based policy, the user keeps their own permissions plus gains the resource access (union). With role assumption, the user's permissions are replaced (only role permissions).
Why can't the Sandbox account assume this role right now?
Your Sandbox OU's allowlist SCP permits only EC2, S3, and IAM actions. The sts:AssumeRole action belongs to the STS service, which is not in the allowlist. This means the SCP would block the member account from actually calling AssumeRole.
In a production environment, you would include sts:AssumeRole in the SCP allowlist for accounts that need to assume cross-account roles. This is a practical example of how SCPs set the maximum permissions ceiling for member accounts.
Role creation failed?
Make sure you selected Custom trust policy as the trusted entity type, not AWS account. The custom option lets you paste the exact trust policy JSON.
Verify the member account ID in the trust policy matches exactly. The ARN format must be arn:aws:iam::<account-id>:root with no spaces.
Help me fix my cross-account role trust policy configuration.
Compare resource-based policies vs. IAM roles for cross-account access
You've now set up both approaches side by side. Understanding when to use each is a key SAA exam topic and a real-world architecture decision.
- Review the comparison below and note how each approach handles the principal's original permissions differently.
Resource-based policy approach
The principal retains their own permissions plus gains the resource access. AWS evaluates identity-based policies and resource-based policies together as a union of permissions.
Use this when: you need the principal to maintain dual access (e.g., copying objects between accounts), or when you want a simple grant without creating additional roles.
IAM role approach
The principal's original permissions are suspended. Only the role's permissions apply while the role is assumed.
Use this when: you need strict permission boundaries, when the target service doesn't support resource-based policies, or when you want to enforce least-privilege by limiting what the cross-account user can do.
Cross-account evaluation requires both sides to allow
For any cross-account request, AWS performs two evaluations: one in the trusting account (where the resource lives) and one in the trusted account (where the principal lives). The request is allowed only if both evaluations return Allow.
This is why your Sandbox SCP matters. Even though the bucket policy grants access to the member account, the member account's SCP must also permit the requested S3 actions. Your allowlist includes S3, so the bucket policy approach works.
You've now seen both cross-account access patterns in action. Next up, you'll zoom out and walk through how AWS evaluates all these policy types together when making an allow or deny decision.
Understand IAM Policy Evaluation Logic
In the previous step, you demonstrated how cross-account access works using both resource-based policies and IAM roles. You saw firsthand that resource-based policies preserve the principal's original permissions, while role assumption replaces them.
Now it's time to zoom out and understand the full picture of how AWS decides whether to allow or deny any given request. When multiple policy types exist simultaneously (SCPs, identity-based policies, resource-based policies, permissions boundaries, session policies), AWS follows a specific evaluation order to reach a final decision.
In this step, get ready to:
- Walk through the IAM policy evaluation flowchart from Explicit Deny through to Session Policies.
- Analyze a real scenario using your existing SCPs, identity-based policies, and bucket policy.
- Document the key evaluation rules that determine same-account and cross-account outcomes.
Walk through the IAM policy evaluation flowchart
Every request to AWS passes through a specific evaluation sequence before receiving an Allow or Deny. Understanding this sequence is critical for predicting access outcomes in complex multi-policy environments.
- Open the IAM Policy Evaluation Logic documentation page in a new browser tab:
- https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.html
- Navigate to the How AWS enforcement code logic evaluates requests to allow or deny access section to view the full evaluation flowchart.
The evaluation follows this sequence for a request within a single account:
- Explicit Deny check: AWS scans all applicable policies (SCPs, RCPs, resource-based, identity-based, permissions boundaries, session policies) for any Deny statement matching the request. If found, the request is immediately denied.
- Organizations SCPs/RCPs: If the account is a member of an AWS Organization, the SCP must allow the action. SCPs set the maximum permissions ceiling for member accounts.
- Resource-based policies: If a resource-based policy grants access to the principal, this can result in an Allow (for IAM users) even without an identity-based policy allowing it.
- Identity-based policies: AWS checks policies attached to the principal (user, group, or role). At least one must explicitly allow the action.
- Permissions boundaries: If a boundary is attached to the entity, it must also allow the action. The result is the intersection of identity-based policies and the boundary.
- Session policies: If the principal is a session principal (role session or federated user session), any passed session policy must also allow the action.
Why does evaluation order matter?
An explicit deny in any policy overrides any allows in any policy. This is the single most important rule. It means a deny in an SCP trumps an allow in an identity-based policy, and vice versa.
The key insight is that SCPs, permissions boundaries, and session policies do NOT grant permissions. They only set the maximum ceiling. The actual permission must come from an identity-based or resource-based policy.
- Open a text editor or note-taking application (any tool you prefer for documentation).
- Create a new document and title it TechCorp IAM Policy Evaluation Analysis.
- Document the evaluation sequence by writing out the six steps listed above in your own words.
What are the seven policy types in AWS?
AWS supports seven types of policies: identity-based policies, resource-based policies, permissions boundaries, Organizations SCPs, Organizations RCPs, access control lists (ACLs), and session policies. In your TechCorp environment, you are working with SCPs, identity-based policies, and resource-based policies so far.
Analyze a scenario combining your SCP, identity-based policy, and bucket policy
Now apply the evaluation logic to your actual TechCorp resources. You have three relevant policies in play: the Sandbox allowlist SCP, an identity-based S3 allow policy, and the bucket policy on techcorp-iam-demo-bucket.
- In your analysis document, add a section titled Scenario Analysis.
- Write out the three policies involved:
- TechCorp-Sandbox-Allowlist-SCP: Allows only EC2, S3, and IAM actions for the Sandbox OU.
- Identity-based policy on a user in the member account: Allows all S3 actions (assume the user has this).
- Bucket policy on techcorp-iam-demo-bucket: Grants s3:GetObject and s3:ListBucket to the member account's root principal.
Now trace through two request scenarios.
Scenario 1: Same-account request (management account user accessing the bucket)
A user in the management account with an identity-based policy allowing S3 access requests s3:GetObject on techcorp-iam-demo-bucket.
Evaluation: SCPs do not affect the management account. The identity-based policy allows s3:GetObject. The bucket policy also allows access. Within the same account, identity-based and resource-based policies are evaluated as a union. Either one allowing is sufficient.
Result: ALLOWED. The identity-based policy alone is enough. The bucket policy provides additional confirmation but is not required for same-account access.
Scenario 2: Cross-account request (sandbox account user accessing the bucket)
A user in the sandbox (member) account with an identity-based policy allowing all S3 actions requests s3:GetObject on techcorp-iam-demo-bucket in the management account.
Evaluation on the member account side: The SCP allows S3 actions (passes the ceiling check). The identity-based policy allows s3:GetObject (passes the permission check). Member account evaluation returns Allow.
Evaluation on the management account side: The bucket policy grants s3:GetObject to the member account root principal. Management account evaluation returns Allow.
Result: ALLOWED. Cross-account access requires BOTH sides to independently allow. Both do in this case.
- Document both scenarios in your analysis, including the evaluation path and outcome.
- Add a third scenario: what happens if the sandbox account user requests s3:DeleteObject on techcorp-iam-demo-bucket?
Scenario 3: Cross-account DeleteObject request denied
The sandbox user's SCP allows S3 actions and their identity-based policy allows all S3 actions. The member account side returns Allow. However, the bucket policy on techcorp-iam-demo-bucket only grants s3:GetObject and s3:ListBucket. It does not grant s3:DeleteObject.
Result: DENIED. The management account side has no policy allowing s3:DeleteObject for the cross-account principal. Cross-account requires both sides to allow, and one side did not.
Document the key evaluation rules
Distill everything you've learned into a set of rules you can reference during the exam and in real-world architecture decisions.
- In your analysis document, add a final section titled Key Evaluation Rules.
- Document these four rules:
- Rule 1: Explicit deny always wins. An explicit Deny in any policy overrides any Allow in any other policy.
- Rule 2: Within the same account, identity-based and resource-based policies are a union. If either allows the action (and nothing denies it), the request is allowed.
- Rule 3: Cross-account access requires BOTH sides to allow. The trusted account's identity-based policies must allow AND the trusting account's resource-based policy must allow.
- Rule 4: SCPs set the maximum permissions ceiling for member accounts. They never grant permissions. An action must be allowed by the SCP AND by an identity-based or resource-based policy.
How do permissions boundaries fit in?
When AWS evaluates the identity-based policies and permissions boundary for a user, the resulting permissions are the intersection of the two categories. This means both the identity-based policy AND the permissions boundary must allow the action. You'll explore this hands-on in the Secret Mission.
- Save your completed analysis document.
- Review it to confirm you have: the evaluation flowchart sequence, all three scenario analyses with outcomes, and the four key rules.
Memorize the formula for the exam
For same-account requests without boundaries: Effective permissions = union of identity-based + resource-based policies, constrained by SCPs. For cross-account: both accounts must independently allow, constrained by SCPs on the member side.
Confused about why SCPs don't affect the management account?
SCPs only affect member accounts in your organization. The management account always retains full permissions regardless of what SCPs are attached. This is by design so you cannot accidentally lock yourself out of your own organization.
If you're unsure whether a policy applies in a given scenario, trace through the flowchart step by step. Help me understand why my policy evaluation scenario isn't matching expected results.
You've now mapped out the full IAM policy evaluation logic and applied it to your own TechCorp architecture. Next, you'll put identity federation into practice by setting up a Cognito User Pool with user authentication.
Set Up Cognito User Pool
You've documented the IAM policy evaluation logic that governs how AWS makes access decisions across your Organization. Now it's time to tackle authentication and identity federation.
Enterprise applications need a way to manage user sign-up, sign-in, and access control without building authentication from scratch. Amazon Cognito solves this by providing a fully managed user directory that issues JWT tokens for authentication and can exchange them for temporary AWS credentials. In this step, you'll create a Cognito User Pool for TechCorp, register a test user, and understand how User Pools integrate with OAuth 2.0 flows and external identity providers.
In this step, get ready to:
- Create a Cognito User Pool with email as the sign-in attribute and self-registration enabled.
- Create an App Client without a client secret and register a test user.
- Understand Cognito User Pool integrations including hosted UI, JWT tokens, and federation.
Create a User Pool and App Client
A Cognito User Pool is a user directory that handles sign-up, sign-in, and token issuance. You'll configure TechCorp's User Pool to let users sign in with their email address and create a public App Client for testing.
- In the AWS Management Console, search for and open Cognito.
- In the left navigation, click User Pools.
- Click Create user pool.
The creation wizard walks you through configuring your User Pool's sign-in and application settings.
- In the Configure sign-in experience section, under Cognito user pool sign-in options, select Email as the sign-in option.
- Leave other settings at their defaults and click Next.
- On the Configure security requirements page, keep the default password policy and MFA settings.
- Under Self-service sign-up, confirm that Enable self-registration is checked.
- Click Next.
- On the Configure sign-up experience page, keep defaults for attribute verification and required attributes.
- Click Next.
- On the Configure message delivery page, select Send email with Cognito for email delivery.
- Click Next.
- On the Integrate your app page, enter TechCorp-UserPool as the User pool name.
- Under Initial app client, choose Single page application as the Application type.
- Enter TechCorp-AppClient as the App client name.
- For the Return URL, enter http://localhost as a placeholder.
- Click Next.
- Review your settings on the Review and create page, then click Create user pool.
Why Single page application?
Selecting Single page application creates a public client without a client secret. Public clients are designed for browser-based or mobile apps where a secret cannot be stored safely.
The console only generates client secrets when you choose Traditional web application or Machine-to-machine application. For testing purposes, a public client without a secret is simpler to work with.
After creation, Cognito displays your new User Pool's details page.
- Note the User pool ID displayed at the top of the page (format: us-east-1_xxxxxxxxx).
- Navigate to the App clients section under Applications in the left menu.
- Note the Client ID for TechCorp-AppClient.
- Confirm that no client secret is listed for this App Client.
Save these IDs
Copy both the User Pool ID and App Client ID to a text file. You'll need them in the next step when you configure the Identity Pool.
Don't see the App Client?
Make sure you're looking under Applications > App clients in the left navigation pane of your User Pool. If you skipped the App Client configuration during pool creation, you can add one by clicking Create app client from this page.
Help me troubleshoot my Cognito App Client setup.
Register and confirm a test user
To test the authentication flow in the next step, you need at least one confirmed user in the pool. You'll create a user directly from the console and confirm them administratively.
- In your User Pool, click Users in the left navigation pane.
- Click Create user.
- Under Invitation message, select Don't send an invitation.
- For Email address, enter a test email such as testuser@example.com.
- Select Set a password and enter a temporary password that meets the policy (at least 8 characters, with uppercase, lowercase, numbers, and a symbol).
- Click Create user.
The user now appears in the list with a Confirmation status of Force change password. You need to confirm the account so it can be used for testing.
- Click on the username you just created to open the user details.
- Open the Actions dropdown menu and select Confirm account.
- Verify that the Confirmation status changes to Confirmed.
What does confirming a user do?
Admin-created users start in a Force change password state, meaning they must change their temporary password on first sign-in. Confirming the account activates it so the user can authenticate.
In a production application, users would confirm their own accounts by entering a verification code sent to their email. For testing, administrative confirmation skips that step.
User stuck in unconfirmed state?
If the Actions menu doesn't show Confirm account, make sure you're viewing the individual user's detail page (click the username link), not the user list page.
Help me confirm my Cognito test user.
Understand Cognito User Pool integrations
Now that your User Pool and test user are set up, take a moment to understand how User Pools integrate with applications in production. These integration patterns are frequently tested on the SAA exam.
What is the hosted UI?
Cognito provides a hosted UI (managed login) for OAuth 2.0 flows. This is a pre-built, customizable sign-in page that handles the entire authentication flow including sign-up, sign-in, MFA challenges, and federation with external providers.
When a user completes sign-in through the hosted UI, Cognito redirects them back to your application's callback URL with an authorization code that your app exchanges for tokens.
What tokens does a User Pool issue?
After successful authentication, Cognito issues three JSON Web Tokens:
- ID token: Contains user attributes (email, name, custom attributes) and is used to create user profiles in your app.
- Access token: Contains scopes that authorize access to APIs and Cognito user self-service operations.
- Refresh token: An encrypted token your app uses to get new ID and access tokens when they expire without requiring the user to sign in again.
How does federation with external identity providers work?
User Pools can federate with external identity providers so users sign in with their existing credentials. Supported provider types include:
- Social providers (Google, Apple, Facebook, Amazon) via OAuth 2.0.
- SAML 2.0 providers (Okta, Microsoft Entra ID, any SAML-compliant IdP) for enterprise workforce identity.
- OIDC providers for any OpenID Connect-compliant identity service.
Regardless of the sign-in method, Cognito normalizes all identity information into a standard set of JWTs. Your application code only needs to handle one token format.
Your Cognito User Pool is ready. Next up, you'll connect this User Pool to a Cognito Identity Pool to grant authenticated users temporary AWS credentials with fine-grained DynamoDB access.
Configure Cognito Identity Pool and DynamoDB Row-Level Security
In the previous step, you created a Cognito User Pool that handles authentication. Now you need the second half of the puzzle: authorization.
A User Pool proves who a user is. An Identity Pool decides what that user can do by exchanging their tokens for temporary AWS credentials. In this step, you'll wire the two together and enforce row-level security on a DynamoDB table so each user can only access their own data.
In this step, get ready to:
- Create a DynamoDB table with sample items using different user IDs.
- Create a Cognito Identity Pool linked to your User Pool and configure the authenticated role with row-level access.
- Test the full authentication and authorization flow end to end.
Create a DynamoDB table with sample items
Your row-level security pattern requires a DynamoDB table where each item belongs to a specific user. The partition key will hold the user's Cognito identity ID, and IAM policy conditions will restrict access to only matching items.
- In the AWS Management Console, search for DynamoDB and open the DynamoDB service.
- Click Create table.
- Enter TechCorp-UserData as the Table name.
- Enter userId as the Partition key.
- Set the partition key type to String.
- Leave all other settings at their defaults and click Create table.
- Wait for the table status to change to Active.
Why userId as the partition key?
The dynamodb:LeadingKeys condition key restricts access based on the partition key value. By making userId the partition key and matching it against the Cognito identity sub, each user can only read or write items where the partition key equals their own identity.
Now add a few sample items to the table so you can test row-level access later.
- Click on your TechCorp-UserData table, then select the Explore table items button.
- Click Create item.
- For userId, enter user-001.
- Click Add new attribute, select String, name it data, and enter User 001 private data as the value.
- Click Create item.
- Repeat to create a second item with userId set to user-002 and data set to User 002 private data.
- Repeat once more with userId set to user-003 and data set to User 003 private data.
You should now see three items in the table, each with a different userId value.
Table stuck in Creating status?
DynamoDB tables typically become active within seconds. If the table stays in Creating status for more than a minute, refresh the page. If it still doesn't resolve, check your region is correct and try again.
Still stuck? Help me troubleshoot my DynamoDB table creation
Create a Cognito Identity Pool and configure the authenticated role
The Identity Pool bridges your User Pool (authentication) with AWS services (authorization). When a user signs in through the User Pool, the Identity Pool exchanges their JWT token for temporary AWS credentials governed by an IAM role.
- In the AWS Console, search for Cognito and open the Amazon Cognito service.
- In the left navigation, click Identity pools.
- Click Create identity pool.
- Enter TechCorp-IdentityPool as the Identity pool name.
- Under User access, choose Authenticated access.
- For the Authenticated identity sources, select Amazon Cognito user pool.
- Click Next.
- On the permissions page, select Create a new IAM role.
- Enter TechCorp-IdentityPool-AuthRole as the role name.
- Click Next.
- Under Connect identity providers, select your TechCorp-UserPool as the User Pool ID.
- Select TechCorp-AppClient as the App client ID.
- Click Next, review your settings, and click Create identity pool.
What is a Cognito Identity Pool?
A Cognito Identity Pool (also called Federated Identities) maps authenticated users to IAM roles. It takes tokens from identity providers like Cognito User Pools, Google, or Facebook, and returns temporary AWS credentials scoped to the permissions of the assigned role.
This is what allows your application users to interact directly with AWS services (like DynamoDB) without needing long-term AWS credentials.
Now configure the authenticated role to enforce row-level security on your DynamoDB table.
- In the AWS Console, search for IAM and open the IAM service.
- Click Roles in the left sidebar.
- Search for and click on TechCorp-IdentityPool-AuthRole.
- Under the Permissions tab, click Add permissions, then Create inline policy.
- Switch to the JSON tab and paste the following policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:Query",
"dynamodb:PutItem"
],
"Resource": "arn:aws:dynamodb:*:*:table/TechCorp-UserData",
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": [
"${cognito-identity.amazonaws.com:sub}"
]
}
}
}
]
}
What does this policy do?
This policy uses a condition on dynamodb:LeadingKeys to restrict which items a user can access. The variable ${cognito-identity.amazonaws.com:sub} resolves to the authenticated user's unique Cognito identity ID at request time.
The result: each user can only GetItem, Query, or PutItem on DynamoDB items where the partition key (userId) matches their own Cognito identity. Items belonging to other users are invisible.
- Click Next.
- Enter TechCorp-DynamoDB-RowLevel as the policy name.
- Click Create policy.
You should see the new inline policy listed under the role's permissions.
Policy validation error?
Make sure you copied the JSON exactly, including the curly braces around ${cognito-identity.amazonaws.com:sub}. This is an IAM policy variable, not a placeholder you replace manually.
Also check that dynamodb:LeadingKeys is spelled correctly. It's a condition key specific to DynamoDB fine-grained access control.
Need more help? Help me fix my DynamoDB row-level security policy
Test the full authentication and authorization flow
The flow you'll test has three stages: authenticate with the User Pool to get tokens, exchange those tokens via the Identity Pool for temporary AWS credentials, and then use those credentials to access DynamoDB. This proves the entire chain works end to end.
You'll use the AWS CLI to walk through each stage. If you don't have the AWS CLI installed, you can use CloudShell from the AWS Console (click the terminal icon in the top navigation bar).
- First, note down your User Pool ID, App Client ID, and Identity Pool ID from the Cognito console. You'll need all three.
- Authenticate your test user to get an ID token by running this command:
aws cognito-idp initiate-auth \
--auth-flow USER_PASSWORD_AUTH \
--client-id [[COGNITO_CLIENT_ID="your App Client ID"]] \
--auth-parameters USERNAME=[[TEST_USER_EMAIL="your test user email"]],PASSWORD=[[TEST_USER_PASSWORD="your test user password"]]
What does this command do?
The initiate-auth command authenticates your user with the User Pool using the USER_PASSWORD_AUTH flow. On success, it returns an AuthenticationResult containing three tokens: an IdToken (user attributes), an AccessToken (scopes), and a RefreshToken.
You need the IdToken for the next step. Copy it from the output.
ALLOW_USER_PASSWORD_AUTH must be enabled
For this command to work, your App Client must have the ALLOW_USER_PASSWORD_AUTH authentication flow enabled. If you get an error, go back to your User Pool, select App clients, edit TechCorp-AppClient, and enable Sign in with username and password: ALLOW_USER_PASSWORD_AUTH under Authentication flows.
- Get a Cognito identity ID by exchanging the ID token with the Identity Pool:
aws cognito-identity get-id \
--identity-pool-id [[IDENTITY_POOL_ID="your Identity Pool ID (format: region:guid)"]] \
--logins cognito-idp.[[AWS_REGION="your region (e.g. us-east-1)"]].amazonaws.com/[[USER_POOL_ID="your User Pool ID"]]=[[ID_TOKEN="paste your IdToken here"]]
What is the logins parameter?
The --logins parameter maps your identity provider to the token. For Cognito User Pools, the provider name follows the format cognito-idp.<region>.amazonaws.com/<user-pool-id> and the value is the ID token from authentication.
The response returns an IdentityId in the format region:guid. Copy this value for the next command.
- Exchange the identity for temporary AWS credentials:
aws cognito-identity get-credentials-for-identity \
--identity-id [[IDENTITY_ID="your IdentityId from previous step (format: region:guid)"]] \
--logins cognito-idp.[[AWS_REGION="your region (e.g. us-east-1)"]].amazonaws.com/[[USER_POOL_ID="your User Pool ID"]]=[[ID_TOKEN="paste your IdToken here"]]
What credentials do I get?
The response contains an AccessKeyId, SecretKey, and SessionToken. These are temporary AWS credentials scoped to the TechCorp-IdentityPool-AuthRole permissions. They expire after one hour.
The DynamoDB row-level policy on that role means these credentials can only access items in TechCorp-UserData where the userId partition key matches this user's IdentityId.
- Export the temporary credentials as environment variables so the AWS CLI uses them:
export AWS_ACCESS_KEY_ID=your-access-key-id-here
export AWS_SECRET_ACCESS_KEY=your-secret-key-here
export AWS_SESSION_TOKEN=your-session-token-here
- Test writing an item using your own identity ID as the partition key:
aws dynamodb put-item \
--table-name TechCorp-UserData \
--item '{"userId": {"S": "your-identity-id-here"}, "data": {"S": "My private data"}}'
Replace your-identity-id-here with the IdentityId you received from the get-id command. This should succeed because the partition key matches your identity.
- Now test accessing another user's data to confirm the deny:
aws dynamodb get-item \
--table-name TechCorp-UserData \
--key '{"userId": {"S": "user-001"}}'
This should fail with an AccessDeniedException because user-001 does not match your Cognito identity ID. Row-level security is working.
Why does the deny happen?
The ForAllValues:StringEquals condition on dynamodb:LeadingKeys checks that the partition key you're requesting matches the policy variable. Since user-001 is not your Cognito identity sub, the condition evaluates to false and the request is denied.
This is the same pattern used in production to isolate tenant data in multi-tenant applications.
Getting AccessDeniedException on your own item too?
Make sure you used the exact IdentityId (the region:guid value from get-id) as the userId partition key value when putting the item. The IdentityId and the partition key must be character-for-character identical.
Also verify the exported credentials match the output from get-credentials-for-identity. If the token expired (after 1 hour), re-run the authentication flow from the beginning.
Still stuck? Help me debug my DynamoDB row-level security test
- Clean up your temporary credentials when finished testing:
unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY
unset AWS_SESSION_TOKEN
You've just built a complete identity federation pipeline. Users authenticate through the User Pool, receive temporary AWS credentials from the Identity Pool, and those credentials restrict DynamoDB access to only their own items. Next, you'll review the enterprise-scale identity services that extend this pattern across large organizations.
Review IAM Identity Center, Directory Services, and Control Tower
You've just built a working Cognito integration that grants authenticated users row-level access to their own DynamoDB items.
Now it's time to zoom out and understand the enterprise-scale identity services that tie everything together. Three AWS services handle workforce identity, directory management, and multi-account governance at scale. In this step, you'll review each one and document how they fit into TechCorp's security architecture.
In this step, get ready to:
- Review AWS IAM Identity Center and its role in centralized SSO access.
- Compare AWS Directory Service options for different enterprise scenarios.
- Understand AWS Control Tower's automated multi-account governance.
Review AWS IAM Identity Center
Managing workforce access across multiple AWS accounts is a challenge that grows with every new account you add. AWS IAM Identity Center solves this by providing centralized single sign-on (SSO) to all your AWS accounts and business applications.
- In the AWS Console, search for IAM Identity Center in the search bar and open the service page.
- Review the dashboard to see the service overview and getting started options.
What is IAM Identity Center?
IAM Identity Center (formerly AWS Single Sign-On, renamed on July 26, 2022) is the recommended way to manage workforce access to AWS. It provides a single place where employees sign in and access all their assigned AWS accounts and applications.
There is no additional charge for IAM Identity Center itself. You only pay for the AWS accounts and resources your users access through it.
IAM Identity Center has three core components you need to understand for enterprise architecture.
First: Permission Sets. These are collections of IAM policies that define what a user can do in an account. When you assign a permission set to a user for a specific account, IAM Identity Center creates an IAM role in that account automatically.
Second: Identity Sources. IAM Identity Center supports three identity source options.
- Built-in directory — create and manage users directly in IAM Identity Center.
- Active Directory — connect to AWS Managed Microsoft AD or on-premises AD via AD Connector.
- External Identity Providers — connect to Okta, Ping Identity, JumpCloud, or Microsoft Entra ID (formerly Azure AD).
Third: Instance Types. IAM Identity Center supports two types of instances: organization instances and account instances. An organization instance is the best practice because it integrates with your entire AWS Organization.
How does this relate to what we built?
In Step 7, you used Cognito for customer-facing authentication (external users signing into an app). IAM Identity Center serves a different purpose. It handles workforce identity, meaning your internal employees accessing AWS accounts and tools.
For TechCorp, Cognito handles their app users while IAM Identity Center manages their developers and admins across multiple AWS accounts.
- Document in your architecture analysis: IAM Identity Center provides centralized SSO via permission sets, supports built-in directory, Active Directory, and external IdPs as identity sources, and uses organization instances as best practice.
Compare AWS Directory Service options
Many enterprises already have Microsoft Active Directory on-premises. AWS Directory Service offers three ways to work with Active Directory in the cloud, each suited to different scenarios.
- In the AWS Console, search for Directory Service and open the service page.
- Review the options available under **Set up a directory**.
Here are the three Directory Service options and when to use each one.
What is AWS Managed Microsoft AD?
AWS Managed Microsoft AD is powered by an actual Microsoft Windows Server Active Directory, managed by AWS in the cloud. It supports trust relationships, schema extensions, MFA via RADIUS, and PowerShell AD cmdlets.
It comes in two editions. Standard Edition supports up to 30,000 Active Directory objects. Enterprise Edition supports up to 500,000 objects with multi-Region support.
Use it for enterprises needing trusts with on-premises AD, RDS SQL Server integration, or more than 5,000 users.
What is AD Connector?
AD Connector is a directory gateway that redirects directory requests to your on-premises Microsoft Active Directory without caching any information in the cloud. No directory data is stored in AWS.
It has a 1-to-1 relationship with on-premises domains and cannot be shared across accounts. Amazon RDS is not compatible with AD Connector. It only works with AWS Managed Microsoft AD.
What is Simple AD?
Simple AD is a Microsoft Active Directory-compatible directory powered by Samba 4. It provides basic AD features like users, groups, Kerberos SSO, and group policies at low cost.
Simple AD does not support multi-factor authentication (MFA), trust relationships, schema extensions, communication over LDAPS, or PowerShell AD cmdlets. It supports a maximum of approximately 5,000 users.
Here is a quick comparison to help you choose the right option for different scenarios.
- Need trusts, RDS integration, or 5,000+ users? Use AWS Managed Microsoft AD.
- Have on-premises AD and want no data in the cloud? Use AD Connector.
- Need basic AD features at low cost with fewer than 5,000 users? Use Simple AD.
- Document in your architecture analysis: the three Directory Service options, their key features, their limitations, and which enterprise scenario each one fits.
Review AWS Control Tower for multi-account governance
In Steps 1 and 2, you manually created an Organization, built OUs, and attached SCPs. AWS Control Tower automates all of this at scale. It provides a pre-configured multi-account environment with built-in governance guardrails.
- In the AWS Console, search for Control Tower and open the service page to see the landing zone overview.
What does Control Tower do?
Control Tower automates multi-account governance by creating a Landing Zone. A Landing Zone is a pre-configured, secure multi-account environment based on AWS best practices.
When you first set up Control Tower, it provisions an account factory, creates 2 shared accounts (Log Archive and Audit), and applies mandatory preventive and detective controls. This setup takes about 30 minutes.
Control Tower is built on top of AWS Organizations. There is no additional charge for Control Tower itself. You only pay for AWS services it enables, such as AWS Service Catalog and AWS CloudTrail.
Control Tower applies two types of governance controls.
- Preventive controls are implemented as SCPs. They prevent actions before they happen. This is the same mechanism you used in Step 2 with your blocklist and allowlist SCPs.
- Detective controls are implemented as AWS Config rules. They detect non-compliant resources after they are created and flag them for remediation.
Why didn't we set up Control Tower in this project?
Control Tower creates 2 additional accounts (Log Archive and Audit) with associated costs from Config, CloudTrail, and S3 storage. It also takes about 30 minutes just to provision the landing zone.
For this project, manually setting up Organizations and SCPs gives you a deeper understanding of how Control Tower works under the hood. In production, you would use Control Tower to automate everything you did in Steps 1 and 2.
There are a few critical Control Tower facts to remember.
- You cannot change your home Region after you've set up your landing zone.
- SCPs applied by Control Tower don't affect the management account, just like the SCPs you created manually.
- Control Tower governance applies only to registered OUs and accounts.
- Document in your architecture analysis: Control Tower's Landing Zone concept, preventive controls (SCPs) vs. detective controls (Config rules), the shared accounts it creates (Log Archive and Audit), and the fact that it is built on Organizations with no additional charge.
How do all three services work together?
In a full enterprise deployment like TechCorp, these services complement each other. Control Tower sets up the multi-account structure with governance guardrails. IAM Identity Center provides SSO access for employees across those accounts. Directory Service connects your existing corporate identity system.
Together, they form the identity layer for workforce access. Cognito (from Step 7) sits alongside them but handles a different audience: your application's end users.
Your enterprise IAM architecture analysis is complete. You now understand the full spectrum of AWS identity services, from the hands-on SCPs and Cognito you built to the enterprise-scale services that tie them together. Next up, take on a challenge that tests everything you've learned by combining multiple policy layers.
Secret mission
Permissions Boundaries and Policy Evaluation Deep Dive
In enterprise environments, you often delegate IAM administration to team leads. But how do you prevent a delegated admin from granting more permissions than they should? Permissions boundaries solve this by capping what any identity-based policy can grant. In this mission, you'll build a boundary, attach it to a delegated admin, and trace through the full policy evaluation logic when multiple policy types collide.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses AWS free tier services, so there are no ongoing costs as long as you stay within free tier limits. The member account could accrue charges if additional resources are created in it.
Cost warning
While the individual resources (S3 bucket, DynamoDB table, Cognito pools) are covered by free tier, the member account is a full AWS account that can independently incur charges. If you or anyone else creates resources in that account, standard AWS pricing applies.
If you are not planning to continue working with this architecture, choose Pause or Delete below to avoid surprises on your bill.
Resources you used:
- AWS Organization with management account and member account.
- Organizational Units: Production OU and Sandbox OU.
- SCPs: TechCorp-Blocklist-SCP and TechCorp-Sandbox-Allowlist-SCP.
- S3 bucket: techcorp-iam-demo-bucket.
- DynamoDB table: TechCorp-UserData.
- Cognito User Pool (TechCorp-UserPool) and Identity Pool (TechCorp-IdentityPool).
- IAM roles: TechCorp-S3-Test-Role, TechCorp-CrossAccount-Role, TechCorp-IdentityPool-AuthRole.
- IAM policies: TechCorp-S3-IP-Region-Policy, TechCorp-S3-MFA-Tag-Policy, TechCorp-DelegatedAdmin-Boundary.
- IAM user: DelegatedAdmin.
Keep everything running
No action needed. Choose this if you are still actively building or plan to revisit this architecture soon.
- All resources remain within free tier limits for typical exam-study usage.
- Monitor the Billing and Cost Management dashboard in your management account to catch any unexpected charges from the member account.
- Remember that the Sandbox OU has FullAWSAccess detached. If you try to use the member account directly for other work, you will be limited to EC2, S3, and IAM actions only.
Pause - I'll come back to this later
Re-attach the default permissions on the Sandbox OU so you do not accidentally lock yourself out, then leave everything else in place.
- In the AWS Organizations console, go to Policies then Service control policies.
- Select TechCorp-Sandbox-Allowlist-SCP, go to the Targets tab, and detach it from the Sandbox OU.
- Navigate to Organizational units, select the Sandbox OU, go to the Policies tab, and attach the FullAWSAccess SCP.
- Leave all other resources (S3 bucket, DynamoDB table, Cognito pools, IAM roles and policies) in place. They will not incur charges within free tier.
Delete - I don't want to use this again
Remove all project resources completely. Follow these steps in order to avoid dependency errors.
Delete IAM user and roles:
- In the IAM console, go to Users, select DelegatedAdmin, and click Delete. Confirm by typing the username.
- Go to Roles and delete each of these roles: TechCorp-S3-Test-Role, TechCorp-CrossAccount-Role, and TechCorp-IdentityPool-AuthRole. Select each role and click Delete.
Delete IAM policies:
- In the IAM console, go to Policies, filter by Customer managed.
- Delete each of these policies: TechCorp-S3-IP-Region-Policy, TechCorp-S3-MFA-Tag-Policy, and TechCorp-DelegatedAdmin-Boundary. Select each policy, click Actions, then Delete.
Delete Cognito resources:
- In the Amazon Cognito console, go to Identity pools, select TechCorp-IdentityPool, and click Delete identity pool.
- Go to User pools, select TechCorp-UserPool, and click Delete user pool. You may need to deactivate deletion protection first in the pool's settings.
Delete DynamoDB table:
- In the DynamoDB console, go to Tables, select TechCorp-UserData, and click Delete. Confirm by typing confirm in the dialog.
Delete S3 bucket:
- In the Amazon S3 console, select techcorp-iam-demo-bucket and click Empty. Confirm by typing permanently delete in the dialog.
- After emptying, select the bucket again and click Delete. Confirm by typing the bucket name.
Detach and delete SCPs:
- In the AWS Organizations console, go to Policies then Service control policies.
- Select TechCorp-Sandbox-Allowlist-SCP, go to the Targets tab, and detach it from the Sandbox OU. Then click Delete policy.
- Select TechCorp-Blocklist-SCP, go to the Targets tab, and detach it from the root. Then click Delete policy.
- Re-attach FullAWSAccess to the Sandbox OU before closing the member account. Go to Organizational units, select Sandbox OU, and attach FullAWSAccess.
Close the member account:
- In the AWS Organizations console, go to AWS accounts, select the member account, and click Close. Enter the account ID to confirm.
- The account enters a 90-day suspended period before permanent deletion. You can reinstate it via AWS Support during this window if needed.
Delete Organizational Units:
- In the AWS Organizations console, go to AWS accounts, expand the root, select the Sandbox OU (after the member account has been moved out or closed), and click Delete.
- Repeat for the Production OU.
Delete the Organization (optional):
- You can only delete the Organization after all member accounts are closed or removed. In the Organizations console, go to Settings and click Delete organization. This reverts your management account to a standalone account.
- If the member account is still in the 90-day suspended period, you cannot delete the Organization until that period completes. You can leave the Organization in place with no cost during this time.
That's a Wrap!
That's a Wrap!
You've just designed and implemented a comprehensive enterprise IAM security architecture for TechCorp!
From multi-account governance to fine-grained row-level access control, you now have hands-on experience with the advanced IAM patterns that define production-grade AWS environments.
What you learned:
- Built a multi-account AWS Organization with Organizational Units governed by blocklist and allowlist SCPs, enforcing maximum permission boundaries across member accounts.
- Wrote IAM policies with conditions (source IP, region, tags, MFA) and implemented cross-account access using both resource-based policies and IAM roles, understanding that resource-based policies preserve original permissions while role assumption suspends them.
- Integrated a Cognito User Pool with an Identity Pool to grant authenticated users temporary AWS credentials scoped to their own DynamoDB items through row-level security.
- Implemented permissions boundaries to limit a delegated admin and traced multi-layer policy evaluation logic across SCPs, identity policies, boundaries, and resource-based policies.
Ready to quiz yourself?