A mid-market medical-device manufacturer finishes a year of acquisitions and finds itself with eleven engineering teams spread across 140 AWS accounts, and an identity model that has quietly become the company’s biggest audit liability. Each acquired company brought its own IAM users, its own static access keys, and its own idea of what “admin” meant. The trigger is a failed SOC 2 Type II observation: the auditor pulls a sample of twelve developers and finds four of them still have active IAM users — with console passwords and long-lived access keys — in accounts they left two reorgs ago. Worse, when a contractor offboarded the previous month, nobody could say with confidence which of the 140 accounts still trusted their credentials. The CISO’s mandate is blunt: one identity per human, sourced from the HR system, with access that appears on day one and vanishes the hour someone leaves — and prove it to the auditor. The company already runs Okta as its workforce identity provider for 300-plus SaaS apps. This article is the reference architecture for making AWS just another app behind that same front door, at the scale of a real multi-account estate.
The pressures here are the ones every growing AWS estate hits eventually. Sprawl: IAM users and access keys multiply per account and nobody ever deletes them. Joiner-mover-leaver: access has to track the org chart automatically, not via a ticket a tired engineer half-completes. Least privilege at scale: you cannot hand-write an IAM policy per person per account 140 times. And auditability: every access decision needs to trace back to a named human, a named group, and a time-bounded session. AWS IAM Identity Center (the service formerly called AWS SSO) federated to Okta is the pattern that satisfies all four — it makes Okta the single source of identity, eliminates per-account IAM users entirely, and replaces them with short-lived role sessions assumed through a central front door.
Why not the obvious shortcuts
Three weaker approaches will get proposed in the first design meeting, and naming why each fails saves a quarter of wasted work.
IAM users per account is where most companies start and the exact state this manufacturer is escaping. Every human needs a credential in every account they touch, keys never rotate, offboarding means hunting through 140 accounts, and the blast radius of one leaked key is unbounded. A single shared “jump” account with cross-account roles improves things — humans log into one account and assume roles elsewhere — but you still manage the humans’ credentials yourself, MFA is your problem, and there is no link to the HR lifecycle, so the leaver problem persists. Federating each account directly to Okta with its own SAML IAM identity provider technically works but explodes operationally: 140 SAML app integrations in Okta, 140 trust relationships to rotate certificates on, and no central place to see or revoke a person’s access.
IAM Identity Center threads the needle. It sits once at the AWS Organizations level, trusts Okta once over SAML, receives users and groups once over SCIM, and projects access into every account through permission sets — reusable role templates it materializes as IAM roles in each target account on your behalf. Humans authenticate to Okta (with Okta’s adaptive MFA and conditional access), land in a single AWS access portal, and assume short-lived roles. There are no IAM users, no static keys, and one revocation point.
Architecture overview
The design has two planes that operate on completely different schedules, and keeping them separate in your head is the key to running it well: a provisioning plane that flows identities from HR through Okta into AWS, and a runtime access plane that a developer exercises every time they reach for the console or CLI.
The defining property of the whole topology is that AWS Organizations owns the account estate, IAM Identity Center is enabled once in the management (or a delegated administrator) account, and Okta is the sole identity source. No IAM Identity Center user is ever created by hand; they all arrive via SCIM. No permission set is assigned to an individual; assignments are always group-to-permission-set-to-account. That discipline is what makes 140 accounts manageable by a two-person platform team.
Provisioning plane, following the flow:
- The company’s HRIS (Workday) is the system of record for who works here. Okta ingests joiner-mover-leaver events from the HRIS, and Okta’s lifecycle rules drop each person into the right Okta groups based on department, role, and team —
aws-payments-dev,aws-platform-admin,aws-billing-readonly, and so on. This is the birthright-provisioning engine: org-chart changes become group memberships automatically. - Okta SCIM pushes users and those groups into IAM Identity Center’s identity store over the SCIM v2 endpoint. A new hire’s account and group memberships appear in AWS within minutes of their HR record going active; a leaver’s are deprovisioned the same way. SCIM is the spine of the leaver story — the thing the auditor’s twelve-developer sample will now pass.
- In IAM Identity Center, a platform engineer (via Terraform, never the console) defines permission sets —
DeveloperAccess,ReadOnlyAuditor,PlatformAdmin,BillingViewer— and creates assignments that bind an Okta-sourced group to a permission set in a specific set of accounts. IAM Identity Center then provisions the corresponding IAM role into each target account automatically.
Runtime access plane, following the flow:
- A developer opens the AWS access portal (or runs
aws sso loginfrom the CLI). The portal redirects to Okta for authentication; Okta applies its policy — password, adaptive MFA, device posture, network zone — and on success issues a SAML assertion back to IAM Identity Center’s ACS endpoint. - IAM Identity Center reads the assertion, looks up the user’s group memberships in its identity store, and presents exactly the accounts and roles that user’s groups are assigned. The developer sees, say, three accounts with
DeveloperAccessand one withReadOnlyAuditor— and nothing else. - The developer picks an account and role. IAM Identity Center calls
sts:AssumeRolebehind the scenes and issues short-lived credentials (default one hour, configurable) scoped to that permission set’s policy — plus any ABAC session tags carried from Okta attributes (more on this below). The developer gets a console session or CLI credentials that expire automatically. - Every assumption is logged to CloudTrail in the target account and centrally, tying a named human to a named role to a timestamp — the audit trail the whole project exists to produce.
Component breakdown
| Component | Service / tool | Role in the architecture | Key configuration choices |
|---|---|---|---|
| Source of truth | Workday (HRIS) | Authoritative joiner-mover-leaver feed | Drives Okta lifecycle; no direct AWS link |
| Identity provider | Okta | Workforce auth, MFA, group engine, SAML IdP | Adaptive MFA; group push; SAML app for IAM Identity Center |
| Provisioning | Okta SCIM → IAM Identity Center | Sync users + groups into AWS identity store | SCIM v2 endpoint + bearer token; group-only push |
| Federation hub | AWS IAM Identity Center | Trusts Okta, hosts permission sets, vends sessions | Org-wide instance; delegated admin; 1–12h session duration |
| Account estate | AWS Organizations | Multi-account boundary and OU structure | OUs by env/team; SCPs as guardrails |
| Access templates | Permission sets | Reusable IAM role definitions per access tier | Managed + inline policies; aws:PrincipalTag ABAC conditions |
| Fine-grained access | ABAC session tags | Tag-based authZ without policy-per-team | Okta attributes → SAML → session tags → resource match |
| Secrets (non-human) | HashiCorp Vault | Dynamic AWS creds for apps/CI, not humans | AWS secrets engine; short TTL; replaces remaining static keys |
| IaC | Terraform | Declarative permission sets + assignments | aws_ssoadmin_* resources; assignments in code review |
| CI/CD | GitHub Actions | Applies Terraform via OIDC, no stored keys | OIDC to a deploy role; plan-gate on PR |
| ITSM | ServiceNow | Access-request workflow for exceptions | Catalog item → Okta group add; change record per grant |
| Posture / CSPM | Wiz | Detects IAM-user drift, over-privilege, public exposure | Agentless org scan; alerts on any new IAM user or key |
| Observability | Datadog | CloudTrail-based access dashboards and anomaly alerts | Sign-in and AssumeRole monitors; SSO failure SLOs |
A few of these choices carry the weight of the design and deserve the why.
Why group-to-permission-set, never user-to-permission-set. It is tempting, especially early, to assign a permission set directly to a single user “just for now.” Do not — it is the seed of the exact sprawl you are escaping. Every assignment should bind an Okta group (sourced from HR attributes) to a permission set in a set of accounts. When someone changes teams, HR updates their record, Okta moves their group membership, SCIM reflects it, and their AWS access changes with zero AWS-side work. The human lifecycle lives entirely in Okta; AWS only ever knows about groups.
Why permission sets instead of hand-written IAM roles. A permission set is a template IAM Identity Center materializes as an identical IAM role across every account you assign it to, and re-syncs if you change it. Update DeveloperAccess once and the change propagates to all 140 accounts’ copies. Without permission sets you are back to maintaining IAM roles by hand, per account, forever.
Why ABAC, and where it earns its keep. With 11 teams and 140 accounts, role-based access alone tempts you toward a combinatorial explosion of permission sets — one per team per tier. ABAC collapses that. You carry a team (and maybe cost-center) attribute from Okta through the SAML assertion as a session tag, write one DeveloperAccess permission set whose policy says “you may act only on resources tagged with the same team as your session,” and let the tag do the partitioning. One template serves every team; the data’s tags enforce the boundary.
Implementation guidance
Enable IAM Identity Center once, at the org level, and delegate administration. Turn it on in the management account, then immediately register a delegated administrator account so day-to-day permission-set and assignment work does not happen in the highly sensitive management account. Choose Identity Center directory as the identity source (you are bringing your own IdP via SAML/SCIM, not Active Directory). This is foundational plumbing — get it placed correctly before anything else.
Wire Okta as the SAML IdP and SCIM provider. In Okta, add the “AWS IAM Identity Center” app integration. Exchange metadata both ways: IAM Identity Center gives you a SAML metadata document and an ACS URL; Okta gives you its IdP metadata, which you upload to IAM Identity Center. Then enable automatic provisioning in IAM Identity Center to get a SCIM endpoint and a bearer token, and paste both into Okta’s provisioning tab. Push groups (not just users), and assign only the Okta groups that should reach AWS — pushing your entire directory is a common and noisy mistake.
SCIM endpoint : https://scim.<region>.amazonaws.com/<tenant-id>/scim/v2/
Bearer token : (rotate on a schedule; store in Vault, not a wiki)
Push scope : aws-* groups only (not the whole Okta directory)
Define permission sets and assignments in Terraform. Never click them into existence — they are access policy and belong in version control and code review. A permission set with an ABAC-aware policy, plus a group-to-account assignment, looks like this:
data "aws_ssoadmin_instances" "main" {}
resource "aws_ssoadmin_permission_set" "developer" {
name = "DeveloperAccess"
instance_arn = tolist(data.aws_ssoadmin_instances.main.arns)[0]
session_duration = "PT1H" # 1-hour sessions; short by design
}
# Team-scoped via ABAC: only act on resources tagged with the caller's team.
resource "aws_ssoadmin_permission_set_inline_policy" "developer_abac" {
instance_arn = aws_ssoadmin_permission_set.developer.instance_arn
permission_set_arn = aws_ssoadmin_permission_set.developer.arn
inline_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Action = ["ec2:*", "s3:*"],
Resource = "*",
Condition = {
StringEquals = { "aws:ResourceTag/team" = "$${aws:PrincipalTag/team}" }
}
}]
})
}
# Bind the Okta-sourced group to the permission set in one account.
resource "aws_ssoadmin_account_assignment" "payments_dev" {
instance_arn = aws_ssoadmin_permission_set.developer.instance_arn
permission_set_arn = aws_ssoadmin_permission_set.developer.arn
principal_id = var.okta_group_payments_dev_id # from SCIM-synced group
principal_type = "GROUP"
target_id = var.payments_account_id
target_type = "AWS_ACCOUNT"
}
The Terraform applies through GitHub Actions authenticating to AWS via OIDC federation to a scoped deploy role — so there is no long-lived AWS access key sitting in a CI secret, which is precisely the failure mode the company is eradicating elsewhere. A terraform plan runs on every PR as a required check, so an access change is reviewed like any other code.
Light up ABAC end to end. Three things must line up, or the tag silently does nothing. (1) In IAM Identity Center, enable attributes for access control and map an attribute named team to the Okta attribute that carries it. (2) In Okta, ensure that attribute is populated on every user (ideally sourced from Workday) and flows in the SAML assertion. (3) Tag your AWS resources with team, and write permission-set policies that compare aws:ResourceTag/team to aws:PrincipalTag/team, as above. When all three align, one DeveloperAccess set safely serves every team; when one is missing, access either over-grants or denies everything, so test it with a canary user per team.
Enterprise considerations
Security & Zero Trust. The architecture is Zero Trust by construction: no standing credentials, identity-based access only, every session short-lived and tied to a named human via CloudTrail. Layer on top: (a) Service Control Policies at the Organizations level as guardrails that even an admin permission set cannot exceed — for example, denying the creation of new IAM users and access keys org-wide, which structurally prevents regressing to the old model; (b) HashiCorp Vault’s AWS secrets engine to issue dynamic, short-TTL credentials to the few non-human consumers (CI jobs, legacy apps) that genuinely cannot use OIDC, retiring the last static keys; © Wiz running continuous posture scanning across the Organization, alerting the moment any IAM user or long-lived key reappears anywhere or a permission set drifts toward over-privilege — the independent backstop that proves the SCPs and policies are actually holding; (d) adaptive MFA and conditional access in Okta as the front-door control, so a risky sign-in (impossible travel, unmanaged device) is challenged or blocked before AWS is ever reached; (e) exceptions routed through ServiceNow — a temporary elevated grant is a catalog request that adds the user to an Okta group with an expiry and leaves a change record, so even break-glass access is auditable. Critically, break-glass for the management account stays out of band: a small number of IAM users with hardware MFA, locked in a vault, for the day Okta or IAM Identity Center itself is unreachable. Federating everything with no escape hatch is how you lock yourself out of your own cloud.
Cost. IAM Identity Center itself is free; the cost story here is operational and risk-driven rather than a per-service bill.
| Lever | Mechanism | Typical effect |
|---|---|---|
| Eliminate IAM-user ops | No keys to rotate, no per-account offboarding | Frees platform-team hours every reorg |
| Group-driven access | HR change → automatic AWS access | Removes most access tickets entirely |
| Audit efficiency | One CloudTrail-backed access story | Shrinks SOC 2 evidence-gathering time |
| Fewer permission sets via ABAC | One template per tier, not per team | Less policy to author and review |
| Reserved Okta licensing | Okta is already licensed for SaaS SSO | AWS rides existing identity spend |
The dominant saving is people-time and audit risk, not an AWS line item: the team stops spending days per quarter chasing stale credentials, and the SOC 2 access control evidence becomes a single export.
Scalability. This pattern is designed to scale precisely where the old one broke. Adding the 141st account means putting it in the right OU and letting existing assignments (which can target an OU or the whole org, not just individual accounts) flow in — no new identity work. Adding a team means a new Okta group and one or two assignments, not a new IAM footprint. The ceilings to watch are AWS quotas — permission sets per instance, accounts per assignment batch — and SCIM throughput for very large directories, which is why you push only aws-* groups rather than the entire company. Session duration is a knob: shorter (one hour) is safer but means more re-auths; longer (up to twelve) is friendlier for long-running work but widens the credential window.
Failure modes, and what each looks like. Name them before they page you.
- SCIM sync lag or breakage — a leaver’s deprovisioning doesn’t propagate, or a new hire can’t get in. The SCIM bearer token silently expiring is the classic cause. Mitigation: monitor SCIM in Datadog, alert on provisioning errors, and rotate the token on a tracked schedule.
- Okta outage — your entire AWS console/CLI access depends on Okta being up. Mitigation: the out-of-band break-glass IAM users in the management account, tested quarterly, are the whole point of keeping that escape hatch.
- ABAC misconfiguration — a missing
teamattribute means a permission set either denies everything or, worse, over-grants. Mitigation: a canary user per team in CI that asserts it can reach its own resources and not another team’s. - Permission-set drift — a console edit diverges from Terraform. Mitigation:
terraform planon a schedule alerts on drift; treat the console as read-only for these resources. - Over-broad SCP or assignment — a too-wide assignment grants admin to the wrong group across many accounts at once. Mitigation: code review on every assignment PR and Wiz flagging the resulting over-privilege.
Reliability & DR (RTO/RPO). IAM Identity Center is a regional AWS service; treat its configuration as the recoverable asset. Because permission sets and assignments live in Terraform, the entire access model is reproducible from code — your real RPO is “whatever is committed to Git,” effectively zero. The runtime dependency is Okta plus the IAM Identity Center region; if either is impaired, the break-glass IAM users are the recovery path for the management account while you wait, and read-only auditors can fall back to those if pre-provisioned. A pragmatic target: RTO of minutes to restore access via break-glass during an Okta incident, and near-zero RPO on the access configuration itself thanks to IaC. Keep the Okta SAML signing certificate’s rotation calendared — an expired cert breaks every AWS login at once and is a self-inflicted outage.
Observability. Pipe CloudTrail (organization trail) and IAM Identity Center sign-in logs into Datadog, and build the dashboards the auditor and the security team actually want: sign-in success/failure rates, AssumeRole events by user and account, permission-set usage (which sets are never used and should be pruned), and anomaly monitors on unusual access — a developer assuming a role in an account they’ve never touched, or a spike in failed sign-ins. Emit an SLO on SSO availability so a degradation in the login path surfaces before users flood the help desk. The same CloudTrail data, exported, is the SOC 2 access-control evidence — one pipeline serves both operations and audit.
Governance. Keep all permission sets and assignments in version control, reviewed via PR, so every access grant has an author, a reviewer, and a timestamp. Run access certifications on a cadence — and since the source of truth is Okta groups, you can review group membership in Okta (or push it to an IGA tool) rather than auditing 140 accounts. Enforce the deny-IAM-user / deny-access-key SCP org-wide so the old model is structurally impossible to reintroduce, with Wiz as the independent verifier that the guardrail holds. Tag-driven ABAC also means your access policy and your cost-allocation tags can share the same team/cost-center taxonomy, so identity and FinOps reinforce each other instead of diverging.
Explicit tradeoffs
Accept these or do not build it. Federating AWS to Okta adds real moving parts: a SAML trust and SCIM pipeline to keep healthy, a hard dependency on Okta for everyday AWS access, and a break-glass discipline you must actually test rather than just document. ABAC is powerful but unforgiving — it only works if the team attribute is populated everywhere and resources are tagged consistently, which forces a tagging hygiene you may not have today. The shift from IAM users is a genuine behavior change for engineers used to long-lived keys; aws sso login and one-hour sessions are an adjustment, and a few stubborn tools that only understand static keys will need Vault-issued dynamic credentials instead. And the whole model presumes a healthy HRIS-to-Okta lifecycle — if your HR data is messy, garbage flows straight into AWS group memberships.
The alternatives, and when they win. If you run a single AWS account and a handful of humans, plain IAM with IAM Identity Center but the built-in directory (no external IdP) may be all you need — the Okta federation earns its complexity at multi-account, multi-team scale. If your organization is already all-in on Microsoft Entra ID rather than Okta, federate IAM Identity Center to Entra instead; the pattern is identical, only the IdP differs. If you need fine-grained, request-time authorization inside applications (not just account/role access), that is a job for an authorization service like Cedar/Verified Permissions, with IAM Identity Center handling only the human-to-AWS boundary. And if you genuinely cannot tolerate the Okta dependency for break-glass at all, you keep a slightly larger set of out-of-band IAM users — accepting more standing credentials in exchange for independence. For a regulated, fast-growing, multi-account estate like this manufacturer’s, though, Okta-federated IAM Identity Center with permission sets and ABAC is where access management has to land.
The shape of the win
For the device manufacturer, the payoff is not “single sign-on.” It is that the next SOC 2 auditor pulls a twelve-developer sample and every one of them has exactly one identity, sourced from HR, with access that materialized on day one and provably disappears the hour they leave — and the evidence is a single CloudTrail export, not a 140-account scavenger hunt. A developer who changes from the payments team to the platform team has their AWS access change automatically because their Okta group changed; a contractor who offboards loses access to all 140 accounts at once because there was only ever one place that granted it. Everything upstream — the SCIM pipeline, the group-to-permission-set discipline, the ABAC session tags, the Vault-issued dynamic creds for the last static-key holdouts, the SCP that bans IAM users, the Wiz scan that proves it — exists to make that single audit sample pass and to make the leaver problem structurally impossible. The architecture here is the destination; if you are escaping IAM-user sprawl, this is where you are heading.