---
cuppy_hub: aws
cuppy_path: guides/identity-center/set-up-sso.md
cuppy_section: docs
cuppy_doc: 7P0E-SFRG
cuppy_doc_uuid: d282438c-2aef-4680-b8f4-6eb55abea664
cuppy_permalink: https://cuppy.io/aws/d/7P0E-SFRG
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/guides/identity-center/set-up-sso
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 1dc57b53e9948ab6055303e161392e8883d920daa551533cff97e096ab0f2f60
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Set up workforce SSO with IAM Identity Center and an external IdP

**Keep users in your IdP and access in AWS.** The IdP (Okta, Entra ID, Google Workspace, …) authenticates people over SAML 2.0 and pushes users and groups into Identity Center over SCIM. Identity Center decides what they can do through permission sets assigned to **groups** on accounts. Users must exist in Identity Center *before* they can sign in. SAML can't create them on the fly.

## 0. Prerequisites

- An **organization instance** of Identity Center, enabled from the Organizations management account (or managed by a delegated administrator). Account assignments need an organization instance. On instances created since August 2026, "AWS account access" may be off: turn it on in Settings, or with `UpdateInstance`, before you create permission sets.
- One identity source per organization. Switching to an external IdP changes how *every* user signs in.

## 1. Exchange SAML metadata

1. **In the IdP**, add the gallery app named **AWS IAM Identity Center** (Okta and Entra ID both have one) and download its IdP metadata XML.
2. **In Identity Center**, go to Settings → Identity source → Actions → **Change identity source** → External identity provider. Copy the **ACS URL** and **issuer URL** (or the service-provider metadata file), upload the IdP's metadata, type `ACCEPT`, and confirm.
3. **Back in the IdP**, set the ACS URL and issuer URL on the app. The username or NameID it sends must be **unique per user** and is what users sign in as.

The rules that decide whether sign-in works:

- The NameID format is `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`, an RFC 2822 address that **equals the Identity Center user name**.
- One assertion per response, and the assertion is not encrypted.
- For SCIM users, the attribute sent as the SAML `Subject` `NameID` must be the same one mapped to **Username** in SCIM.

## 2. Turn on SCIM provisioning

1. In Identity Center Settings, enable **Automatic provisioning**. Copy the **SCIM endpoint** (`https://scim.<region>.amazonaws.com/<id>/scim/v2`, or dual-stack `scim.<region>.api.aws`) and the **access token**. This is the only time both are shown.
2. In the IdP's provisioning settings, enter them (Okta: Base URL / API Token; Entra ID: Tenant URL / Secret Token, mode *Automatic*), test the connection, and save.
3. **Assign** users or groups to the app in the IdP. Only assigned users sync. In Okta, also **push** the groups, using a *different* group from the one used for assignment. In Entra ID, start provisioning, and remember that **nested groups are not provisioned**: only direct members of assigned groups sync.

Each synced user needs a **first name, last name, username and display name** with single values (one email, one phone). Map a stable, unique `externalId` (an immutable object ID), so renames don't orphan a user's access.

- **The SCIM token lasts one year.** Reminders begin 90 days before expiry. When it expires, sync stops silently. Put the rotation date in a calendar.
- Once SCIM is on, edit users in the IdP, not in the Identity Center console. Delete users there too: deleting in Identity Center breaks the IdP's push.

## 3. Create permission sets and assign groups

Console: Multi-account permissions → Permission sets → Create (start with a predefined one such as `PowerUserAccess`), then AWS accounts → select accounts → Assign users or groups.

TypeScript, AWS SDK for JavaScript v3:

```ts
import {
  SSOAdminClient,
  ListInstancesCommand,
  CreatePermissionSetCommand,
  AttachManagedPolicyToPermissionSetCommand,
  CreateAccountAssignmentCommand,
  DescribeAccountAssignmentCreationStatusCommand,
  ProvisionPermissionSetCommand,
} from "@aws-sdk/client-sso-admin";
import { IdentitystoreClient, GetGroupIdCommand } from "@aws-sdk/client-identitystore";

const region = "<Identity Center Region>";
const sso = new SSOAdminClient({ region });
const ids = new IdentitystoreClient({ region });

const inst = (await sso.send(new ListInstancesCommand({}))).Instances?.[0];
const InstanceArn = inst?.InstanceArn!;
const IdentityStoreId = inst?.IdentityStoreId!;

const ps = await sso.send(new CreatePermissionSetCommand({
  InstanceArn,
  Name: "Developer",
  SessionDuration: "PT4H", // ISO 8601; 1 h minimum, 12 h maximum
}));
const PermissionSetArn = ps.PermissionSet?.PermissionSetArn!;

await sso.send(new AttachManagedPolicyToPermissionSetCommand({
  InstanceArn,
  PermissionSetArn,
  ManagedPolicyArn: "arn:aws:iam::aws:policy/PowerUserAccess",
}));

// Groups come from your IdP via SCIM; look one up by its display name.
const { GroupId } = await ids.send(new GetGroupIdCommand({
  IdentityStoreId,
  AlternateIdentifier: { UniqueAttribute: { AttributePath: "displayName", AttributeValue: "aws-developers" } },
}));

const req = await sso.send(new CreateAccountAssignmentCommand({
  InstanceArn,
  PermissionSetArn,
  PrincipalType: "GROUP",
  PrincipalId: GroupId!,
  TargetType: "AWS_ACCOUNT",
  TargetId: "<12-digit account id>",
}));

let status = req.AccountAssignmentCreationStatus;
while (status?.Status === "IN_PROGRESS") {
  await new Promise((r) => setTimeout(r, 3_000));
  status = (await sso.send(new DescribeAccountAssignmentCreationStatusCommand({
    InstanceArn,
    AccountAssignmentCreationRequestId: status.RequestId!,
  }))).AccountAssignmentCreationStatus;
}
console.log(status?.Status, status?.FailureReason);

// Later, after editing the permission set through the API, push the change to every account:
await sso.send(new ProvisionPermissionSetCommand({ InstanceArn, PermissionSetArn, TargetType: "ALL_PROVISIONED_ACCOUNTS" }));
```

- Assignments are **asynchronous**. Poll the status as shown. `ConflictException` means an earlier write hasn't propagated yet: retry with backoff.
- **API and IaC edits to a permission set are not pushed automatically.** Call `ProvisionPermissionSet`. The console reprovisions for you.
- Assign **groups**, not users, so membership changes in the IdP flow through SCIM without AWS changes. The `SessionDuration` format is ISO 8601 (for example `PT8H`; format per the SSO Admin API model, the example is the hub's); the documented range is 1–12 hours.

Each assignment creates a role named `AWSReservedSSO_<PermissionSet>_<suffix>` in the account (`reference/identity-center/provisioned-roles-and-sessions.md`).

## 4. Verify

1. As a test user, open the AWS app from the IdP's dashboard (IdP-initiated sign-in) **and** go to the access portal URL (SP-initiated sign-in). Both should land in the portal, showing the assigned account and permission set.
2. CLI: `aws configure sso` (session name, start URL, Region, scope `sso:account:access`), then `aws sso login --profile <p>`, then `aws sts get-caller-identity --profile <p>`. The ARN should show `assumed-role/AWSReservedSSO_…`. Details: `guides/iam/local-credentials.md`.
3. Multiple Regions: add each additional Region's ACS URL to the IdP app (Okta: *Other Requestable SSO URLs*; Entra ID: *Reply URLs*).

## If it fails

Look the exact message up in `reference/identity-center/errors.md`. To see the assertion Identity Center sends to an application, hold **Shift** while choosing the app tile in the portal. For failed external-IdP sign-ins, filter CloudTrail on `ExternalIdPDirectoryLogin`.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
