The short version: enable IAM Identity Center in the management account of your AWS Organization, connect your identity provider (or use the built-in directory), create a few permission sets (Admin, PowerUser, ReadOnly, Billing), assign groups to accounts, and switch everyone's CLI to aws configure sso. Then delete the IAM users. Keep one documented break-glass path for when SSO itself is broken.
AWS doesn't charge extra for IAM Identity Center, and once it is in place, onboarding and offboarding an engineer becomes a change in one directory rather than a hunt across accounts.
Before you start: you need AWS Organizations
Enable Identity Center from the management account of an AWS Organization. That creates an organization instance, which is the only kind that can grant access across multiple AWS accounts.
AWS also offers account instances for a single standalone or member account, but according to the AWS comparison of instance types they don't support multi-account permissions or access-portal sign-in to AWS accounts. For workforce access to AWS, you want the organization instance.
If you don't have an organization yet, set one up first, with at least separate accounts for production and everything else. We explain the account structure we recommend in AWS Organizations from day one.
Step 1: enable Identity Center and pick a region
Sign in to the management account, open the IAM Identity Center console in the region where you want it to live, and choose Enable. For a company operating in the EU, choose the EU region you already run in, such as eu-central-1 or eu-west-1, so the identity store sits next to the rest of your estate.
Two settings to change straight away:
- Delegated administrator. Register a member account (a security or shared-services account) as the delegated administrator, so day-to-day administration happens outside the management account. AWS's delegated administration guide recommends this precisely to keep as few people as possible in the management account. Note that a delegated admin can't modify permission sets provisioned in the management account; that stays with the management account by design.
- Access portal URL. Customise the
d-xxxxxxxxxx.awsapps.com/startsubdomain to something your team will remember, such as your company name.
Step 2: choose the identity source
If your company already runs Google Workspace or Microsoft Entra ID, use it as an external identity provider so that leaving the company removes AWS access automatically. Use the built-in Identity Center directory only if you have no central directory at all.
| Identity source | Sign-in and MFA | User provisioning | Groups |
|---|---|---|---|
| Identity Center directory (built-in) | Managed in Identity Center; turn on MFA in settings | Manual (console, API, Terraform) | Managed in Identity Center |
| Google Workspace | SAML; MFA enforced by Google | SCIM, users only | Not synced; create manually or use ssosync |
| Microsoft Entra ID | SAML; MFA and Conditional Access by Entra | SCIM | Synced via SCIM |
The Google Workspace limitation catches people out. AWS's Google Workspace setup guide states that SCIM sync from Google is limited to users and that automatic group provisioning isn't supported. You can create groups in Identity Center with the Identity Store API (or Terraform, below) and manage membership there, or run AWS's open-source ssosync tool to mirror Google groups. For a team of ten, managing three or four groups in Terraform is usually simpler than running another sync job.
Microsoft Entra ID provisions both users and groups over SCIM, so group membership in Entra drives AWS access directly. See the Entra ID tutorial.
When you switch to an external IdP, the console shows you a SCIM endpoint and access token exactly once. Copy them into the IdP before closing the dialog, and put a calendar reminder on the token's rotation.
Step 3: create groups and permission sets
A permission set is a template of IAM policies. When you assign it to an account, Identity Center creates a matching IAM role in that account. Start with four, mapped to how people actually work, and assign them to groups, never to individuals (with one exception, below).
| Permission set | AWS managed policy | Who gets it | Session |
|---|---|---|---|
| AdministratorAccess | AdministratorAccess | Platform team, in non-prod; one or two people in prod | 1 hour |
| PowerUserAccess | PowerUserAccess | Engineers, in dev and staging | 8 hours |
| ReadOnlyAccess | ReadOnlyAccess | Engineers in production, on-call | 8 hours |
| Billing | job-function/Billing | Founders, finance | 4 hours |
Session duration defaults to one hour and can go up to twelve, per the session duration documentation. Keep admin sessions short, so a forgotten terminal doesn't stay privileged all day.
PowerUserAccess allows everything except IAM and Organizations management, which is a good default for building things in a dev account. In production, read-only by default and changes through CI is the healthier pattern; your deploy pipeline should use its own role via OIDC rather than a person's session (see deploying from GitHub Actions without access keys).
As the team grows, replace the broad managed policies with custom permission sets, for example a "Developer" set that can read logs, connect to ECS tasks and nothing more in production.
Step 4: assign access to accounts
An assignment is a triple: group, permission set, account. A typical startup matrix looks like this:
| Group | dev / staging | production | management |
|---|---|---|---|
| platform-admins | AdministratorAccess | AdministratorAccess | Users assigned directly |
| engineers | PowerUserAccess | ReadOnlyAccess | none |
| finance | none | none | Billing |
The management account is the exception to "groups only". AWS recommends assigning users, not groups, to permission sets there, because anyone who can edit group membership in your IdP would otherwise control who reaches the most privileged account in the organization.
Step 5: manage it in Terraform
Click-ops is fine for the first test, but permission sets and assignments belong in code, reviewed like any other change. Run this from the management account or the delegated administrator account.
data "aws_ssoadmin_instances" "this" {}
locals {
instance_arn = tolist(data.aws_ssoadmin_instances.this.arns)[0]
identity_store_id = tolist(data.aws_ssoadmin_instances.this.identity_store_ids)[0]
permission_sets = {
AdministratorAccess = { policy = "arn:aws:iam::aws:policy/AdministratorAccess", duration = "PT1H" }
PowerUserAccess = { policy = "arn:aws:iam::aws:policy/PowerUserAccess", duration = "PT8H" }
ReadOnlyAccess = { policy = "arn:aws:iam::aws:policy/ReadOnlyAccess", duration = "PT8H" }
Billing = { policy = "arn:aws:iam::aws:policy/job-function/Billing", duration = "PT4H" }
}
# group, permission set, account ID
assignments = [
{ group = "engineers", set = "PowerUserAccess", account = "111111111111" }, # dev
{ group = "engineers", set = "ReadOnlyAccess", account = "222222222222" }, # prod
{ group = "platform-admins", set = "AdministratorAccess", account = "111111111111" },
{ group = "platform-admins", set = "AdministratorAccess", account = "222222222222" },
]
}
resource "aws_ssoadmin_permission_set" "this" {
for_each = local.permission_sets
name = each.key
instance_arn = local.instance_arn
session_duration = each.value.duration
}
resource "aws_ssoadmin_managed_policy_attachment" "this" {
for_each = local.permission_sets
instance_arn = local.instance_arn
permission_set_arn = aws_ssoadmin_permission_set.this[each.key].arn
managed_policy_arn = each.value.policy
}
resource "aws_identitystore_group" "this" {
for_each = toset(["engineers", "platform-admins", "finance"])
identity_store_id = local.identity_store_id
display_name = each.key
}
resource "aws_ssoadmin_account_assignment" "this" {
for_each = { for a in local.assignments : "${a.group}-${a.set}-${a.account}" => a }
instance_arn = local.instance_arn
permission_set_arn = aws_ssoadmin_permission_set.this[each.value.set].arn
principal_type = "GROUP"
principal_id = aws_identitystore_group.this[each.value.group].group_id
target_type = "AWS_ACCOUNT"
target_id = each.value.account
}
If your groups come from Entra ID over SCIM, don't create them in Terraform; look them up with the aws_identitystore_group data source instead, so Terraform and SCIM don't fight over the same objects. With Google Workspace or the built-in directory, the resource above is the right approach. We keep this in its own Terraform stack with its own state, as described in how we organise Terraform state.
Step 6: switch the CLI to SSO
Every engineer runs aws configure sso once, then aws sso login when their session expires. No access keys on disk.
$ aws configure sso
SSO session name (Recommended): acme
SSO start URL [None]: https://acme.awsapps.com/start
SSO region [None]: eu-central-1
SSO registration scopes [None]: sso:account:access
The CLI opens a browser, you pick an account and role, and it writes a profile plus a shared sso-session block to ~/.aws/config. Add more profiles by hand so they reuse the same session:
[sso-session acme]
sso_start_url = https://acme.awsapps.com/start
sso_region = eu-central-1
sso_registration_scopes = sso:account:access
[profile acme-dev]
sso_session = acme
sso_account_id = 111111111111
sso_role_name = PowerUserAccess
region = eu-central-1
[profile acme-prod]
sso_session = acme
sso_account_id = 222222222222
sso_role_name = ReadOnlyAccess
region = eu-central-1
aws sso login --sso-session acme
aws sts get-caller-identity --profile acme-prod
Since AWS CLI 2.22.0, sign-in uses PKCE authorization by default; on a machine without a browser, add --use-device-code. Details are in the AWS CLI SSO guide. Terraform, the AWS SDKs and most tools read these profiles directly, so AWS_PROFILE=acme-dev terraform plan just works.
Step 7: remove IAM users and long-lived keys
SSO only improves security once the old way in is gone. Do it deliberately, account by account.
- Generate a credential report in each account (
aws iam generate-credential-report, thenaws iam get-credential-report) and list every IAM user, password and access key with its last-used date. - Sort them into people (move to SSO), CI systems (move to OIDC roles) and applications (move to instance, task or pod roles).
- Deactivate keys before deleting them. Wait a week, watch for breakage, then delete the keys and the users.
- Add a guardrail: an alert on
CreateAccessKeyandCreateUserevents in CloudTrail, or an SCP that denies them outside a break-glass role.
In our experience the stragglers are always the same: a third-party tool configured years ago with an admin key, and a cron job on someone's server. Find them before you delete, not after.
Step 8: plan break-glass access
If your IdP is down, or someone misconfigures Identity Center, you still need a way in. Decide it now, write it down and test it twice a year.
- Management account root user. Strong unique password, hardware MFA keys (register more than one), credentials in a physical or shared-vault safe that needs two people to open. Alarm on every root sign-in.
- Member account root users. With centralized root access management you can delete root credentials from member accounts entirely and perform the rare root-only tasks through short-lived privileged sessions from the management account. New accounts created in the organization then have no root credentials at all.
- An emergency IAM user or role (optional). Some teams keep one IAM user in the management account, MFA-protected, with credentials sealed alongside the root ones and an alert on any use. It is the one IAM user you should still have.
Write the procedure as a short runbook: who can open the safe, how to sign in, what to fix, and how to rotate the credentials afterwards. Our incident response runbook template has a section for exactly this.
A rollout order that works
- Enable Identity Center, register the delegated admin, connect the IdP.
- Create the four permission sets and groups in Terraform.
- Give the platform team access first and have them use only SSO for a week.
- Onboard engineers, then finance.
- Remove IAM users and keys, add guardrails, test break-glass.
For a small team this takes days, not weeks, and it is one of the controls auditors and enterprise customers check first, whether you are working towards SOC 2 or NIS2. Identity is the first layer of the cloud foundations we put in place for clients; if you would rather have it designed and rolled out for you, alongside account structure and CI access, that is part of our cloud migration and foundations service.
Frequently asked questions
What is the difference between AWS SSO and IAM Identity Center?
They are the same service. AWS renamed AWS Single Sign-On to IAM Identity Center in 2022. The CLI still uses the sso name, so commands like aws configure sso and aws sso login, and the sso_ settings in ~/.aws/config, refer to IAM Identity Center.
Can I use Google Workspace as the identity source for IAM Identity Center?
Yes, via SAML for sign-in and SCIM for user provisioning. AWS documents that SCIM sync from Google Workspace covers users only and does not provision groups, so you create groups in Identity Center yourself (console, API or Terraform) or use the open-source ssosync tool.
How long can an IAM Identity Center session last?
Each permission set has a session duration that defaults to one hour and can be set up to twelve hours. Keep administrator permission sets short and use longer sessions only for lower-risk roles such as read-only access.
Should we delete all IAM users after enabling IAM Identity Center?
Delete IAM users for people and CI systems, moving people to SSO and pipelines to OIDC roles. Keep a documented break-glass path: the management account root user with hardware MFA, and optionally one tightly controlled emergency IAM user with alerts on every use.