Federation and Single Sign-On in the Cloud

Federation is the arrangement by which a cloud provider trusts an external identity provider (IdP) to vouch for who a user is, and — on the strength of that vouching — mints a set of short-lived, scoped cloud credentials for them. Its point is to break the tie between “a human (or an external system) that needs cloud access” and “a permanent cloud secret sitting in that human’s hands.” Instead of every engineer holding a long-lived AWS access key, a Google service-account key file, or an Azure client secret, they authenticate once to the corporate IdP (Okta, Microsoft Entra ID, Ping, Active Directory Federation Services, Google Workspace), and the cloud hands back a session that expires in an hour or a workday. This note is the cloud-side of that handshake: how the trust relationship is declared, how a Security Token Service (STS) exchanges an external assertion for temporary credentials, and why humans should never hold static keys. The wire-level mechanics of the assertions themselves — how a SAML 2.0 <Assertion> is signed, how an OpenID Connect (OIDC) ID token’s claims and iss/aud/sub are validated — belong to the Authentication MOC; here we pick up where the IdP has already authenticated the user and asks the cloud, “now give this person credentials.”

Boundary with the Authentication MOC

This note owns the cloud handoff: the trust object you create in IAM, the STS credential-exchange call, and the temporary-credential lifecycle. It deliberately does not re-teach SAML 2.0 or OIDC as protocols — the redirect/POST bindings, assertion signing, ID-token vs access-token distinction, PKCE, and discovery documents live in Authentication MOC. When this note says “the IdP returns a signed SAML assertion” or “the IdP issues an OIDC ID token,” follow the protocol depth there.


Mental Model — the cloud does not authenticate you, it trusts someone who did

The single idea to hold onto: in a federated setup, the cloud provider is not the authenticator. It never sees the user’s password, never runs the multi-factor prompt, never owns the account lifecycle. All of that is delegated to an external IdP the organization already runs. The cloud’s job shrinks to two things: (1) hold a trust anchor — the IdP’s signing metadata or OIDC issuer URL, registered ahead of time — and (2) run a token exchange — accept a freshly-signed assertion, verify it against that anchor, and vend back temporary credentials whose permissions are governed by a role the customer defined.

flowchart LR
    subgraph ORG["Your organization"]
        U["Human user<br/>(or external workload)"]
        IDP["Identity Provider<br/>Okta · Entra ID · Ping<br/>ADFS · Google Workspace"]
    end
    subgraph CLOUD["Cloud provider"]
        TRUST["Trust anchor<br/>SAML metadata / OIDC issuer<br/>registered in IAM"]
        STS["Security Token Service<br/>(STS-style exchange)"]
        ROLE["Role / permission set<br/>defines what the session can do"]
        CREDS["Temporary credentials<br/>expire in minutes–hours"]
    end

    U -->|"1. authenticate<br/>(password + MFA)"| IDP
    IDP -->|"2. signed assertion / ID token"| U
    U -->|"3. present assertion"| STS
    TRUST -.->|"validates signature"| STS
    ROLE -.->|"scopes permissions"| STS
    STS -->|"4. mint"| CREDS
    CREDS -->|"5. call cloud APIs"| ROLE

What it shows and the insight to take: the credential the user finally wields (box 4) is derived, not stored. The only durable secret in the whole picture lives inside the IdP (the user’s password) and inside the cloud’s trust anchor (the IdP’s public signing key — not a secret at all). Nowhere does a long-lived cloud access key get created, copied to a laptop, or checked into a repo. Kill the user in the IdP and every future exchange fails; the blast radius of a compromised session is bounded to its short lifetime.


Why humans must never hold long-lived access keys

Every provider now says this in almost the same words, and the reasoning is worth stating mechanically rather than as a slogan. A long-lived credential — an AWS IAM user access key, a downloaded GCP service-account JSON key, an Entra app client secret — has four fatal properties:

  1. It does not expire on its own. An AWS access key is valid until someone explicitly deactivates it. A leaked key from a git commit three years ago still works today unless it was manually revoked.
  2. It is bearer-only and copyable. Possession is authorization. There is no second factor at the moment of use, no binding to a device, no “was this really the user?” check. Anyone who reads the string is the user.
  3. It is invisible in the org’s identity system. A standalone access key is not tied to the joiner-mover-leaver lifecycle. When an employee leaves and their Okta account is disabled, a static AWS key they created still functions — the two systems don’t know about each other.
  4. Rotation is manual and therefore rare. Because rotating means updating every place the key is embedded, teams avoid it, and keys live for years.

Federation removes all four. The credential the user gets is (1) time-boxed — AWS AssumeRoleWithSAML credentials default to one hour and cap at the role’s maximum session duration of 1 to 12 hours (AWS STS AssumeRoleWithSAML); (2) issued only after a fresh IdP authentication that can demand MFA; (3) governed by the IdP’s lifecycle, since no assertion is minted for a disabled account; and (4) rotated automatically because it is re-minted on each login. AWS’s own guidance frames IAM Identity Center as providing “one point of federation” so that “you only federate once” rather than provisioning per-user keys (AWS IAM Identity Center). Microsoft states flatly that manually-handled secrets “are a known source of security issues and outages” and that managed/federated identities “eliminate the need for developers to manage these credentials” (Entra managed identities).

Uncertain

Verify: the exact default and maximum session-duration numbers for GCP and Azure human federation (I pinned AWS at 1 h default / 1–12 h max from the STS API reference, and the GCP/Azure machine-token lifetime at ~1 h from their metadata docs, but the provider docs fetched here did not state an explicit maximum session length for a human Workforce Identity Federation / Entra SSO session). Reason: the workforce/SSO pages fetched described the exchange as returning a “short-lived” token without a stated ceiling. To resolve: consult GCP’s Security Token Service quota page and Entra ID’s Conditional Access / token-lifetime configuration docs. #uncertain


The SAML 2.0 federated exchange (workforce SSO)

Security Assertion Markup Language (SAML) 2.0 is the older, enterprise-heavy federation standard, and it remains the default for workforce single sign-on — humans logging into the cloud console. The flow below is AWS’s AssumeRoleWithSAML, but Google’s Workforce Identity Federation and Azure’s Entra-fronted app federation follow the same shape: authenticate at the IdP, receive a signed assertion, hand it to the cloud’s token endpoint, receive temporary credentials.

sequenceDiagram
    participant U as User's browser
    participant IdP as Corporate IdP<br/>(Okta / ADFS / Entra)
    participant STS as AWS STS
    participant AWS as AWS service (S3, EC2…)

    U->>IdP: 1. Sign in (password + MFA)
    Note over IdP: IdP looks up which AWS<br/>roles this user may assume
    IdP-->>U: 2. Signed SAML assertion<br/>(base64), lists Role ARN +<br/>SAML-provider ARN
    U->>STS: 3. AssumeRoleWithSAML<br/>(SAMLAssertion, RoleArn,<br/>PrincipalArn) — NO AWS creds needed
    Note over STS: Validates signature against the<br/>SAML provider metadata registered<br/>in IAM; checks role trust policy
    STS-->>U: 4. Credentials {AccessKeyId,<br/>SecretAccessKey, SessionToken,<br/>Expiration} — default 1 h
    U->>AWS: 5. Signed API calls with<br/>temporary credentials
    AWS-->>U: 6. Response (bounded by role's permissions)

What it shows and the insight to take: step 3 is the crux — AssumeRoleWithSAML “does not require the use of AWS security credentials”; the caller’s identity “is validated by using keys in the metadata document that is uploaded for the SAML provider entity” (AWS STS). The user proves nothing to AWS directly. They present a document AWS can verify was signed by a party AWS was pre-configured to trust. The two required inputs — RoleArn (which role to become) and PrincipalArn (which registered SAML provider vouched for you) — plus the base64 SAMLAssertion, are all the API needs.

Two IAM objects make this work, and they are the cloud-side setup this note owns:

  • A SAML identity-provider entity in IAM. You upload the IdP’s SAML metadata document (its entity ID and public signing certificate). This is the trust anchor; without it, STS has no key to verify the assertion’s signature against.
  • An IAM role with a trust policy naming that provider as a trusted principal (e.g. "Principal": {"Federated": "arn:aws:iam::123456789012:saml-provider/Okta"}) and permitting the sts:AssumeRoleWithSAML action. The role’s permission policies then bound what any resulting session can do.

The returned session honors the SAML assertion’s own clock: credentials last for the DurationSeconds you request “or until the time specified in the SAML authentication response’s SessionNotOnOrAfter value, whichever is shorter” (AWS STS). The assertion can also carry attributes as session tags (up to 50), enabling attribute-based access control, and a SourceIdentity that persists across role chaining for audit.

AWS IAM Identity Center (renamed from AWS Single Sign-On on 26 July 2022) is the managed layer most organizations now use instead of wiring AssumeRoleWithSAML by hand. It connects one external IdP once (SAML for authentication, SCIM for user/group sync), defines reusable permission sets (a permission set becomes an IAM role in each target account), and gives users a web access portal to pick an account+role and receive temporary credentials — the same STS temporary-credential model, packaged. Its roles are reserved (AWSReservedSSO_…) and, notably, cannot be assumed via raw AssumeRoleWithSAML (AWS IAM Identity Center).

GCP Workforce Identity Federation is the direct analogue: it lets “external workforce users — employees, partners, contractors — access Google Cloud” via SSO “without requiring Google Accounts,” using a “sync-less” model that stores no user accounts in Google Cloud (GCP Workforce Identity Federation). You create a workforce identity pool and a provider inside it describing the external IdP (OIDC or SAML 2.0 — Entra ID, Okta, ADFS, Ping). Users present IdP credentials to Google’s Security Token Service, which “verifies the identity and returns a short-lived Google Cloud access token in exchange,” following the OAuth 2.0 Token Exchange spec (RFC 8693). IdP claims map to Google attributes (google.subject is mandatory; google.groups, google.display_name optional).

Azure Entra ID occupies a slightly different position because for most Azure shops Entra ID is the IdP as well as the cloud directory — so “federation into Azure” often means federating a third-party IdP (or another Entra tenant) into Entra ID, after which Entra issues the tokens Azure Resource Manager and Microsoft Graph accept. The credential-free machine story (managed identities, workload identity federation) is covered in Workload and Instance Identity; the human-SSO story is standard Entra ID application SSO, which lives with the Authentication MOC protocol depth.


The OIDC / web-identity exchange (workloads and modern apps)

OpenID Connect (OIDC), built on OAuth 2.0, is the JSON/JWT-based successor and the standard for machine and CI/CD federation — GitHub Actions, GitLab, Kubernetes, mobile/web apps. On AWS the entry point is AssumeRoleWithWebIdentity, which “returns a set of temporary security credentials for users who have been authenticated … with a web identity provider” — “any OpenID Connect-compatible identity provider” (AWS STS AssumeRoleWithWebIdentity).

sequenceDiagram
    participant W as Workload<br/>(GitHub Actions job)
    participant OIDC as OIDC IdP<br/>(GitHub's token service)
    participant STS as AWS STS
    participant AWS as AWS service

    W->>OIDC: 1. Request OIDC ID token (JWT)
    OIDC-->>W: 2. Signed JWT — iss, aud,<br/>sub=repo:org/app:ref:main
    W->>STS: 3. AssumeRoleWithWebIdentity<br/>(WebIdentityToken=JWT,<br/>RoleArn, RoleSessionName)
    Note over STS: Fetches IdP's JWKS via the<br/>registered OIDC provider;<br/>verifies JWT signature;<br/>matches sub/aud against<br/>role trust-policy conditions
    STS-->>W: 4. Temporary credentials<br/>(default 1 h)
    W->>AWS: 5. Signed API calls

What it shows and the insight to take: the token in step 2 is a self-contained, IdP-signed JWT — no browser redirect, no human. The role’s trust policy pins which subject may assume it (e.g. only the main branch of org/app), so a leaked workflow file in a fork cannot mint your credentials. Like its SAML sibling, the call “does not require the use of AWS security credentials”; validation is by “a token from the web identity provider” (AWS STS). Tokens must be signed with RSA (RS256/384/512) or ECDSA (ES256/384/512). The response echoes SubjectFromWebIdentityToken (the JWT sub) and Audience (the aud/client ID) for audit.

The setup mirrors SAML: register an OIDC identity provider in IAM (its issuer URL and audience), then create a role whose trust policy trusts that provider and constrains sub/aud claims. GCP’s Workload Identity Federation provides the same for GCP (“access Google Cloud resources … instead of a service account key,” via workload identity pools/providers and the STS token exchange — see Workload and Instance Identity), and Azure’s Workload Identity Federation configures a federated identity credential on an app registration or user-assigned managed identity so the workload “exchanges trusted tokens from the external IdP for access tokens from Microsoft identity platform” (Entra Workload Identity Federation). Azure requires the credential’s issuer, subject, and audience to case-sensitively match the incoming token’s claims, and stores only the first 100 signing keys from the IdP’s OIDC endpoint.

Because AssumeRoleWithWebIdentity straddles both notes — it is the human-app and the workload federation call — the deeper workload-identity treatment (instance metadata, IRSA, managed identities) is in Workload and Instance Identity. This note’s concern is the federated case: an external IdP the cloud was told to trust.


The three vendors’ federation mechanisms side by side

ConcernAWSGCPAzure
Human/workforce SSOIAM Identity Center (SAML + SCIM); or raw AssumeRoleWithSAMLWorkforce Identity Federation (workforce identity pools; OIDC/SAML)Entra ID application SSO; federate 3rd-party IdP into Entra
Workload/CI federationAssumeRoleWithWebIdentity + IAM OIDC providerWorkload Identity Federation (workload identity pools/providers)Workload Identity Federation (federated identity credential on app / user-assigned MI)
Trust anchor objectIAM SAML provider (metadata) or OIDC provider (issuer URL)Workforce/workload identity pool + providerFederated identity credential (issuer/subject/audience)
Token exchange serviceAWS STSGoogle STS (RFC 8693 token exchange)Microsoft identity platform (/oauth2/token, client-credentials w/ federated credential)
ResultSTS temporary credentials (AccessKeyId/SecretAccessKey/SessionToken)Short-lived Google access token (direct access or SA impersonation)Entra access token (bearer JWT)
Session lifetimeDefault 1 h; role max 1–12 h“Short-lived” (STS token, commonly ≤1 h) #uncertain“Short-lived” access token (commonly ~1 h) #uncertain
ProtocolsSAML 2.0, OIDCSAML 2.0, OIDC, plus X.509, AWS/Azure identitiesOIDC (federated credential flow)

What it shows and the insight to take: the vocabulary differs but the machine is identical everywhere — a pre-registered trust anchor, a token-exchange endpoint, and a short-lived derived credential scoped by a role/permission set. Learn the pattern once; the three product names are just skins over it.


Common misunderstandings and failure modes

  • “Federation logs you into the cloud.” No — it logs you into the IdP, which then vouches for you to the cloud. The cloud only verifies a signature and applies a role. If you find yourself typing a cloud-native password, you are not federated.
  • Over-broad trust policies. The classic OIDC federation bug is a role trust policy that trusts the provider but forgets to pin the sub/aud. For GitHub Actions this means any repo’s workflow can assume your role. Always constrain the subject claim (repo:org/app:ref:refs/heads/main) and the audience. AWS explicitly warns about the analogous Cognito case: a trust policy without the :aud condition “creates a risk that a user from an unintended identity pool can assume the role” (AWS STS).
  • PII in the subject/NameID. Both AWS APIs warn that the NameID (SAML) or sub (OIDC) lands in CloudTrail logs; use an opaque persistent identifier, not an email, to avoid leaking PII (AWS STS AssumeRoleWithSAML).
  • Clock skew and expiry. SAML assertions and OIDC tokens carry NotOnOrAfter/exp. A drifting IdP or cloud clock produces ExpiredToken/InvalidIdentityToken errors. The fix is NTP discipline, not longer token lifetimes.
  • Confusing federation with directory sync. SCIM provisioning (pushing users/groups into IAM Identity Center) is not the authentication path; it just pre-creates the principals and their group memberships. Authentication still happens per-login via SAML/OIDC. GCP Workforce Identity Federation is deliberately “sync-less” to avoid this coupling.
  • Assuming Identity Center roles via STS. AssumeRoleWithSAML “will not work on AWS IAM Identity Center managed roles” (the AWSReservedSSO_ ones) — a common trip-up when scripting against Identity-Center-provisioned access (AWS IAM Identity Center).

When to choose which

  • SAML 2.0 — reach for it for human workforce SSO into consoles when your IdP is enterprise-grade (ADFS, Entra ID, Okta, Ping) and the ecosystem already speaks SAML. It is verbose XML but battle-tested for browser-mediated login.
  • OIDC — the default for everything machine-to-machine and modern: CI/CD (GitHub/GitLab), Kubernetes workloads, mobile/SPA apps. JWTs are compact, JSON-native, and don’t need a browser. New integrations should prefer OIDC.
  • Managed federation front-ends (IAM Identity Center, Workforce/Workload Identity Federation, Entra federated credentials) over hand-rolled AssumeRole* calls — they centralize the trust config, add group-to-permission mapping, and give an audit trail, at the cost of a little less low-level control.
  • Per-service note boundary: if the “identity” is a piece of running code on a cloud VM/pod that needs credentials without any external IdP at all (an EC2 instance reading S3, a GKE pod calling BigQuery), that is not federation — it is Workload and Instance Identity, where the cloud platform itself vends the credential.

Production notes

Federation is now the default posture, not an advanced option. AWS’s Well-Architected security guidance and its own tooling push organizations off IAM users entirely and onto IAM Identity Center; the presence of long-lived IAM user access keys is a standard audit finding. GitHub’s OIDC integration with AWS/GCP/Azure is the canonical modern example — it eliminated the once-ubiquitous practice of pasting cloud keys into CI secrets, and its rollout (2021 onward) is why “store your AWS key in GitHub Secrets” is now considered an anti-pattern. The recurring real-world incident is the over-permissive OIDC trust policy: teams trust the GitHub OIDC provider but forget the subject condition, and a fork or a renamed repo gains production access. Pin the sub. The second recurring incident is residual static keys: an organization federates its humans but leaves a handful of service accounts on downloaded keys “just for that one script,” and those are what leak. The endgame is zero long-lived credentials — federation for humans and external systems, workload identity for code running inside the cloud.


See Also