<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>SAML Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/saml/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/saml/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Sat, 09 May 2026 18:38:56 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>SAML Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/saml/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>Identity Providers Explained: On-Prem, Cloud, SCIM, and Federation</title>
		<link>https://linuxcent.com/identity-providers-explained/</link>
					<comments>https://linuxcent.com/identity-providers-explained/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Fri, 08 May 2026 11:00:00 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Entra ID]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[Identity Provider]]></category>
		<category><![CDATA[Okta]]></category>
		<category><![CDATA[SAML]]></category>
		<category><![CDATA[SCIM]]></category>
		<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1802</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span>Identity providers explained: on-prem IdPs (AD FS, Keycloak), cloud IdPs (Okta, Entra ID), SCIM provisioning, SAML federation, and directory sync vs identity federation.</p>
<p>The post <a href="https://linuxcent.com/identity-providers-explained/">Identity Providers Explained: On-Prem, Cloud, SCIM, and Federation</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>The Identity Stack, Episode 11</em><br />
<a href="/saml-vs-oidc-vs-oauth2/">EP10: SAML/OIDC</a> → <strong>EP11</strong> → <a href="/entra-id-linux-login/">EP12: Entra ID + Linux</a> → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>An Identity Provider (IdP) is the system that authenticates users and issues identity assertions (SAML assertions, OIDC tokens) to applications</li>
<li>On-prem IdPs: AD FS (Microsoft), Shibboleth (universities), Keycloak (open source), Ping Identity — they sit in front of AD and speak SAML/OIDC to cloud apps</li>
<li>Cloud IdPs: Okta, Entra ID (Azure AD), Google Workspace, Ping Identity Cloud — they are the directory and the authentication layer in one</li>
<li>Federation: IdPs can trust each other — a corporate IdP can delegate to a cloud IdP, or federate with a partner org&#8217;s IdP</li>
<li>SCIM (System for Cross-domain Identity Management) is provisioning, not authentication — it creates/updates/deactivates user accounts in target systems when the source directory changes</li>
<li>The key distinction: federation (authentication flow) vs directory sync (data copy) — they solve different problems and are often deployed together</li>
</ul>
<hr />
<h2 id="the-big-picture-where-idps-sit">The Big Picture: Where IdPs Sit</h2>
<pre><code class="" data-line="">                        On-prem Directory
                        (Active Directory / OpenLDAP / FreeIPA)
                               │
                               │ LDAP / Kerberos
                               ▼
                         Identity Provider
                         ┌──────────────────────────────────┐
                         │  AD FS / Keycloak / Okta /       │
                         │  Entra ID Connect / Shibboleth   │
                         │                                  │
                         │  Speaks: SAML 2.0 + OIDC + OAuth2│
                         └────────────────┬─────────────────┘
                                          │ assertions / tokens
                      ┌───────────────────┼───────────────────┐
                      ▼                   ▼                   ▼
               Salesforce          GitHub Enterprise      AWS IAM
               (SAML SP)           (OIDC RP)              (OIDC)
</code></pre>
<p>EP10 covered the protocols. This episode covers the systems — what an IdP actually does, how the major ones differ, and how they connect to each other through federation and SCIM.</p>
<hr />
<h2 id="on-premises-identity-providers">On-Premises Identity Providers</h2>
<h3 id="ad-fs-active-directory-federation-services">AD FS (Active Directory Federation Services)</h3>
<p>AD FS is Microsoft&#8217;s on-prem federation server — a Windows Server role that sits in front of Active Directory and speaks SAML 2.0 and OIDC to external applications.</p>
<p>What it does:<br />
&#8211; Authenticates users against AD (Kerberos/LDAP behind the scenes)<br />
&#8211; Issues SAML assertions and OIDC tokens to external SPs<br />
&#8211; Handles claims transformation: maps AD attributes to what the SP expects</p>
<p>What it doesn&#8217;t do well:<br />
&#8211; It&#8217;s Windows Server only<br />
&#8211; Configuration is complex (XML, certificates, claim rule language)<br />
&#8211; No built-in MFA (requires Azure MFA or a third-party provider)<br />
&#8211; Being deprecated in favor of Entra ID for most use cases</p>
<p>AD FS made sense when everything was on-prem. As workloads move to cloud, Entra ID Connect (a lighter sync agent) combined with Entra ID as the IdP replaces AD FS for most enterprises.</p>
<h3 id="keycloak">Keycloak</h3>
<p>Keycloak is the open-source IdP from Red Hat. It&#8217;s what FreeIPA uses for web-based OIDC/SAML SSO, and it&#8217;s widely deployed independently for organizations that want full control over their identity infrastructure.</p>
<pre><code class="" data-line=""># Run Keycloak in development mode (Docker)
docker run -p 8080:8080 \
  -e KEYCLOAK_ADMIN=admin \
  -e KEYCLOAK_ADMIN_PASSWORD=admin \
  quay.io/keycloak/keycloak:latest \
  start-dev

# Keycloak concepts:
# Realm     — an isolated namespace (like a tenant)
# Client    — an application that uses Keycloak for auth (SP/RP)
# User federation — connect Keycloak to an existing LDAP/AD directory
# Identity brokering — federate with external IdPs (Google, GitHub, another SAML IdP)
</code></pre>
<p>Keycloak reads users from AD/LDAP via its User Federation feature — it doesn&#8217;t replace the directory, it federates it. Users still live in AD; Keycloak issues SAML/OIDC tokens based on those users.</p>
<h3 id="shibboleth">Shibboleth</h3>
<p>Shibboleth is the dominant IdP in academia. Most universities run it. It&#8217;s SAML-native, designed for federation between institutions — a student can authenticate at their home university&#8217;s IdP and access resources at a partner institution.</p>
<hr />
<h2 id="cloud-identity-providers">Cloud Identity Providers</h2>
<h3 id="okta">Okta</h3>
<p>Okta is a cloud IdP + directory. It can:<br />
&#8211; Act as the primary user directory (storing users, credentials)<br />
&#8211; Connect to on-prem AD via the Okta Active Directory Agent (a lightweight sync service)<br />
&#8211; Federate with other IdPs (act as IdP or SP in a SAML/OIDC chain)<br />
&#8211; Enforce MFA, Adaptive Authentication, Device Trust</p>
<p>Okta&#8217;s Lifecycle Management handles provisioning: when a user is created/disabled in Okta (or synced from AD), Okta can automatically create/deactivate accounts in downstream SaaS apps — via SCIM or app-specific APIs.</p>
<h3 id="entra-id-azure-active-directory">Entra ID (Azure Active Directory)</h3>
<p>Entra ID is Microsoft&#8217;s cloud IdP. It&#8217;s both a directory (stores users, groups) and an IdP (issues tokens). For organizations running on-prem AD, Entra ID Connect syncs users from AD to Entra ID.</p>
<p>Entra ID is OIDC and OAuth2 native — it speaks SAML for legacy apps but JWT/OIDC for everything modern. Its OIDC implementation follows the standard closely; its token validation happens via <code class="" data-line="">/.well-known/openid-configuration</code> and the JWKS endpoint.</p>
<pre><code class="" data-line="">On-prem AD  →  Entra ID Connect (sync agent)  →  Entra ID (cloud)
                                                      │
                                              SAML / OIDC
                                                      │
                                            SaaS apps, Azure resources
</code></pre>
<h3 id="google-workspace">Google Workspace</h3>
<p>Google Workspace is Google&#8217;s combined directory + IdP. Google accounts are the users. Apps integrate via SAML or OIDC. Google&#8217;s OIDC implementation is one of the most widely used reference implementations — most OIDC libraries are tested against it.</p>
<hr />
<h2 id="federation-idps-trusting-each-other">Federation: IdPs Trusting Each Other</h2>
<p>Federation is the mechanism that lets IdPs delegate to each other. Two patterns:</p>
<h3 id="saml-federation-idp-to-idp">SAML Federation (IdP-to-IdP)</h3>
<p>Common in academia and partner integrations:</p>
<pre><code class="" data-line="">User at University A → requests resource at University B
                              │
                              │ doesn&#039;t know user
                              ▼
                    University B SP redirects to...
                    Discovery Service: &quot;which IdP are you from?&quot;
                              │
                              ▼
                    University A IdP authenticates user
                              │
                    Sends SAML assertion to University B SP
</code></pre>
<p>University B&#8217;s SP trusts University A&#8217;s IdP because both are members of a SAML federation (e.g., InCommon in the US, eduGAIN globally). The federation metadata aggregates all members&#8217; SAML metadata — certificates, endpoints — so members don&#8217;t have to manually configure each bilateral trust.</p>
<h3 id="oidc-identity-brokering">OIDC Identity Brokering</h3>
<p>Keycloak, Okta, and Entra ID can all act as identity brokers — they sit between the application and the actual authenticating IdP:</p>
<pre><code class="" data-line="">App (OIDC RP) → Keycloak (broker IdP) → Google / GitHub / SAML IdP
                                               │ authenticate
                                               ▼
                                      Keycloak receives assertion
                                      Maps external claims to local claims
                                      Issues OIDC token to app
</code></pre>
<p>The app only knows Keycloak. Keycloak handles the upstream IdP complexity.</p>
<hr />
<h2 id="scim-provisioning-authentication">SCIM: Provisioning ≠ Authentication</h2>
<p>SCIM (RFC 7644) is a REST API standard for user lifecycle management — creating, updating, and deactivating user accounts in a target system when changes happen in the source directory.</p>
<pre><code class="" data-line="">Source (Okta / Entra ID)           Target (Slack / GitHub / Jira)
         │                                    │
         │  SCIM 2.0 (REST + JSON)            │
         ├─ POST /Users  ─────────────────────► create user
         ├─ PATCH /Users/id ──────────────────► update attributes
         └─ DELETE /Users/id ─────────────────► deactivate account
</code></pre>
<p>SCIM is not SSO. A SCIM-provisioned user in Slack can log in to Slack — but the authentication still goes through the IdP (SAML/OIDC). SCIM ensures the account exists. The IdP proves the user&#8217;s identity.</p>
<p>Why both? Because SSO alone doesn&#8217;t create accounts in target systems — it just authenticates to them. If a user tries to log in to Slack for the first time via SSO, Slack needs an account to map them to. SCIM creates that account before the first login (Just-in-Time provisioning handles it at first login, but SCIM handles it in bulk and handles deprovisioning reliably).</p>
<p>Deprovisioning is where SCIM matters most. When an employee leaves, you disable them in Okta — SCIM deactivates their account in every connected app within minutes. Without SCIM, IT runs a manual checklist. Someone misses Jira. The ex-employee has access for three weeks.</p>
<hr />
<h2 id="directory-sync-vs-federation">Directory Sync vs Federation</h2>
<p>These are commonly confused:</p>
<p><strong>Directory sync</strong> — copy user data from source to target. Entra ID Connect copies users from on-prem AD to Entra ID. This is not authentication; it&#8217;s data replication. After sync, Entra ID has its own copy of the user record.</p>
<p><strong>Federation</strong> — delegate authentication to an external IdP. The target system doesn&#8217;t store credentials; it redirects to the IdP for authentication and trusts the assertion that comes back.</p>
<p>You often need both:<br />
&#8211; Sync: so the target system has the user record and can enforce policies (group membership, license assignment)<br />
&#8211; Federation: so the user authenticates against the source of truth (your IdP) rather than maintaining a separate password in every system</p>
<hr />
<h2 id="common-misconceptions"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Common Misconceptions</h2>
<p><strong>&#8220;SCIM is an authentication protocol.&#8221;</strong> SCIM is a provisioning protocol. It creates and manages accounts. Authentication is SAML/OIDC. Both solve different parts of the identity lifecycle problem.</p>
<p><strong>&#8220;SSO means you only have one password.&#8221;</strong> SSO means you only authenticate once per session. The password still exists (at the IdP). SSO reduces the number of authentication events, not the number of credentials.</p>
<p><strong>&#8220;On-prem IdP + cloud sync is the same as a cloud IdP.&#8221;</strong> With on-prem IdP + cloud sync (e.g., AD + Entra ID Connect), authentication happens via the on-prem IdP — if it goes down, cloud SSO breaks. A pure cloud IdP (Okta standalone, Entra ID without on-prem AD) authenticates entirely in the cloud.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Relevance</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP Domain 5: Identity and Access Management</td>
<td>IdPs are the central control plane for federated identity — their architecture, trust relationships, and provisioning workflows define the enterprise IAM posture</td>
</tr>
<tr>
<td>CISSP Domain 1: Security and Risk Management</td>
<td>SCIM-based deprovisioning is an access control risk management practice — without it, terminated employee access persists across connected systems</td>
</tr>
<tr>
<td>CISSP Domain 3: Security Architecture and Engineering</td>
<td>The choice of on-prem vs cloud IdP, federation vs sync, and SCIM vs JIT provisioning are architectural decisions with long-term operational and security implications</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>An IdP authenticates users and issues assertions (SAML) or tokens (OIDC/OAuth2) — applications trust the IdP, not the user directly</li>
<li>On-prem: AD FS (Windows/legacy), Keycloak (open source, flexible), Shibboleth (academia)</li>
<li>Cloud: Okta (cloud-native, strong lifecycle management), Entra ID (Microsoft-integrated), Google Workspace</li>
<li>Federation = authentication delegation between IdPs; Directory sync = data replication; SCIM = account lifecycle (provisioning/deprovisioning)</li>
<li>SCIM deprovisioning is the critical control — it ensures ex-employees lose access automatically across all connected systems</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP11 covered the IdP landscape. EP12 gets specific: Entra ID and Linux — how you configure a Linux VM to accept SSH logins authenticated against Azure AD credentials, and how the <code class="" data-line="">aad-auth</code> / <code class="" data-line="">pam_aad</code> stack works end to end.</p>
<p><em>Next: <a href="/entra-id-linux-login/">Entra ID Linux Login: SSH Authentication with Azure AD Credentials</a></em></p>
<p>Get EP12 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fidentity-providers-explained%2F&amp;linkname=Identity%20Providers%20Explained%3A%20On-Prem%2C%20Cloud%2C%20SCIM%2C%20and%20Federation" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fidentity-providers-explained%2F&amp;linkname=Identity%20Providers%20Explained%3A%20On-Prem%2C%20Cloud%2C%20SCIM%2C%20and%20Federation" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fidentity-providers-explained%2F&amp;linkname=Identity%20Providers%20Explained%3A%20On-Prem%2C%20Cloud%2C%20SCIM%2C%20and%20Federation" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fidentity-providers-explained%2F&amp;linkname=Identity%20Providers%20Explained%3A%20On-Prem%2C%20Cloud%2C%20SCIM%2C%20and%20Federation" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fidentity-providers-explained%2F&amp;linkname=Identity%20Providers%20Explained%3A%20On-Prem%2C%20Cloud%2C%20SCIM%2C%20and%20Federation" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fidentity-providers-explained%2F&amp;linkname=Identity%20Providers%20Explained%3A%20On-Prem%2C%20Cloud%2C%20SCIM%2C%20and%20Federation" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fidentity-providers-explained%2F&amp;linkname=Identity%20Providers%20Explained%3A%20On-Prem%2C%20Cloud%2C%20SCIM%2C%20and%20Federation" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fidentity-providers-explained%2F&#038;title=Identity%20Providers%20Explained%3A%20On-Prem%2C%20Cloud%2C%20SCIM%2C%20and%20Federation" data-a2a-url="https://linuxcent.com/identity-providers-explained/" data-a2a-title="Identity Providers Explained: On-Prem, Cloud, SCIM, and Federation"></a></p><p>The post <a href="https://linuxcent.com/identity-providers-explained/">Identity Providers Explained: On-Prem, Cloud, SCIM, and Federation</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/identity-providers-explained/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1802</post-id>	</item>
		<item>
		<title>SAML vs OIDC vs OAuth2: Which Protocol Handles Which Identity Problem</title>
		<link>https://linuxcent.com/saml-vs-oidc-vs-oauth2/</link>
					<comments>https://linuxcent.com/saml-vs-oidc-vs-oauth2/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Fri, 08 May 2026 05:00:00 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Identity Federation]]></category>
		<category><![CDATA[OAuth2]]></category>
		<category><![CDATA[OIDC]]></category>
		<category><![CDATA[SAML]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SSO]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1799</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span>SAML vs OIDC vs OAuth2: understand what problem each solves, when to use which, and how a browser SSO flow works end-to-end — from IdP redirect to JWT claim.</p>
<p>The post <a href="https://linuxcent.com/saml-vs-oidc-vs-oauth2/">SAML vs OIDC vs OAuth2: Which Protocol Handles Which Identity Problem</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>The Identity Stack, Episode 10</em><br />
<a href="/active-directory-ldap-kerberos/">EP09: Active Directory</a> → <strong>EP10</strong> → <a href="/identity-providers-explained/">EP11: Identity Providers</a> → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>SAML 2.0 is a federation protocol for browser-based SSO — an IdP issues a signed XML assertion that a Service Provider trusts; designed for enterprise applications</li>
<li>OAuth2 is an <em>authorization</em> delegation protocol, not authentication — it lets an application act on your behalf without knowing your password; the access token says what, not who</li>
<li>OIDC (OpenID Connect) = OAuth2 + an identity layer — adds the <code class="" data-line="">id_token</code> (a JWT containing who you are) on top of OAuth2&#8217;s <code class="" data-line="">access_token</code> (what you can do)</li>
<li>SAML vs OIDC: SAML is XML, enterprise-native, stateful; OIDC is JSON/JWT, API-native, stateless — new applications almost always use OIDC</li>
<li>The <code class="" data-line="">id_token</code> is a JWT — decode it at jwt.io and read every claim — it tells you exactly what the IdP asserts about the user</li>
<li>The browser SSO flow is three redirects: user → SP → IdP (authenticate) → SP (consume assertion)</li>
</ul>
<hr />
<h2 id="the-problem-ldap-and-kerberos-dont-cross-the-internet">The Problem: LDAP and Kerberos Don&#8217;t Cross the Internet</h2>
<p>EP09 showed how authentication works inside a corporate network. LDAP and Kerberos both assume network proximity to the directory server — firewall-friendly ports don&#8217;t help when the authentication protocol requires a direct connection to the KDC or directory.</p>
<pre><code class="" data-line="">Internal network: works
  Browser → intranet app → LDAP/Kerberos → AD DC (all on 10.0.0.0/8)

Internet: breaks
  Browser → SaaS app (AWS) → LDAP/Kerberos → AD DC (on-prem behind firewall)
  ✗ KDC not reachable across NAT
  ✗ LDAP not exposed to internet (shouldn&#039;t be)
  ✗ Every SaaS app can&#039;t have its own LDAP connection to your DC
</code></pre>
<p>SAML was invented in 2002 to solve this. OIDC in 2014. Both let identity assertions travel over HTTPS — the one protocol that crosses every firewall.</p>
<hr />
<h2 id="saml-20-enterprise-browser-sso">SAML 2.0: Enterprise Browser SSO</h2>
<p>SAML 2.0 has three actors: the User, the Identity Provider (IdP), and the Service Provider (SP).</p>
<pre><code class="" data-line="">1. User visits SP (e.g., Salesforce)
   SP: &quot;I don&#039;t know this user — send them to the IdP&quot;
   ↓  HTTP redirect with SAMLRequest (base64-encoded AuthnRequest)

2. User arrives at IdP (e.g., Okta, AD FS, Entra ID)
   IdP: &quot;Authenticate me&quot; → user enters credentials
   IdP: generates a signed SAML Assertion (XML)
   ↓  HTTP POST to SP&#039;s Assertion Consumer Service (ACS) URL

3. SP receives the SAMLResponse
   SP: verifies the signature using IdP&#039;s public key
   SP: extracts user attributes from the Assertion
   SP: creates a session — user is logged in
</code></pre>
<p>The SAML Assertion is an XML document signed by the IdP. It contains:</p>
<pre><code class="" data-line="">&lt;saml:Assertion&gt;
  &lt;saml:Issuer&gt;https://idp.corp.com&lt;/saml:Issuer&gt;
  &lt;saml:Subject&gt;
    &lt;saml:NameID Format=&quot;urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress&quot;&gt;
      vamshi@corp.com
    &lt;/saml:NameID&gt;
  &lt;/saml:Subject&gt;
  &lt;saml:Conditions
    NotBefore=&quot;2026-04-27T01:00:00Z&quot;
    NotOnOrAfter=&quot;2026-04-27T01:05:00Z&quot;&gt;  ← short-lived: replay protection
  &lt;/saml:Conditions&gt;
  &lt;saml:AttributeStatement&gt;
    &lt;saml:Attribute Name=&quot;email&quot;&gt;
      &lt;saml:AttributeValue&gt;vamshi@corp.com&lt;/saml:AttributeValue&gt;
    &lt;/saml:Attribute&gt;
    &lt;saml:Attribute Name=&quot;groups&quot;&gt;
      &lt;saml:AttributeValue&gt;engineers&lt;/saml:AttributeValue&gt;
      &lt;saml:AttributeValue&gt;sre-team&lt;/saml:AttributeValue&gt;
    &lt;/saml:Attribute&gt;
  &lt;/saml:AttributeStatement&gt;
&lt;/saml:Assertion&gt;
</code></pre>
<p>The SP trusts the assertion because it&#8217;s signed with the IdP&#8217;s private key, and the SP has the IdP&#8217;s public certificate configured. No direct connection between SP and IdP needed during authentication — only the browser carries the assertion.</p>
<p><strong>SP-initiated vs IdP-initiated:</strong><br />
&#8211; SP-initiated: user visits the SP, gets redirected to IdP, authenticates, redirected back — the common flow<br />
&#8211; IdP-initiated: user starts at the IdP (e.g., company portal), clicks an app, IdP sends assertion directly — simpler but no SP-generated RequestID, so the SP can&#8217;t verify the request was expected (a security concern)</p>
<hr />
<h2 id="oauth2-authorization-delegation-not-authentication">OAuth2: Authorization Delegation (Not Authentication)</h2>
<p>This distinction is important and consistently confused: <strong>OAuth2 is for authorization, not authentication.</strong></p>
<p>OAuth2 solves: &#8220;I want to let GitHub Actions post to my Slack without giving GitHub my Slack password.&#8221;</p>
<pre><code class="" data-line="">Resource Owner (you)  → grants permission to →  Client (GitHub Actions)
                                                        │
                                                        │ access_token
                                                        ▼
                                               Resource Server (Slack API)
                                               &quot;this token can post messages&quot;
</code></pre>
<p>The <code class="" data-line="">access_token</code> answers &#8220;what can this client do?&#8221; not &#8220;who is this user?&#8221; A resource server receiving an access token knows the token is valid and what scopes it carries — it does not necessarily know which human authorized it.</p>
<p>The four OAuth2 grant types:</p>
<table>
<thead>
<tr>
<th>Grant</th>
<th>Use case</th>
</tr>
</thead>
<tbody>
<tr>
<td>Authorization Code</td>
<td>Web apps (server-side) — most secure, recommended</td>
</tr>
<tr>
<td>PKCE (+ Auth Code)</td>
<td>Native/SPA apps — Auth Code without client secret</td>
</tr>
<tr>
<td>Client Credentials</td>
<td>Machine-to-machine (no user) — service accounts</td>
</tr>
<tr>
<td>Device Code</td>
<td>Devices without browsers (smart TVs, CLIs)</td>
</tr>
</tbody>
</table>
<p>The Implicit grant (tokens in URL fragment) is deprecated. Don&#8217;t use it.</p>
<hr />
<h2 id="oidc-oauth2-who-you-are">OIDC: OAuth2 + Who You Are</h2>
<p>OpenID Connect adds identity to OAuth2 by adding the <code class="" data-line="">id_token</code> — a JWT that the IdP signs and that contains claims about the authenticated user.</p>
<pre><code class="" data-line="">Authorization Code flow with OIDC:

1. Client redirects user to IdP:
   GET /authorize?
     response_type=code
     &amp;client_id=myapp
     &amp;scope=openid email profile    ← &quot;openid&quot; scope triggers OIDC
     &amp;redirect_uri=https://app.com/callback
     &amp;state=random-nonce

2. IdP authenticates user, returns:
   GET /callback?code=AUTH_CODE&amp;state=random-nonce

3. Client exchanges code for tokens:
   POST /token
   grant_type=authorization_code&amp;code=AUTH_CODE...

4. IdP returns:
   {
     &quot;access_token&quot;: &quot;eyJ...&quot;,    ← what the user authorized
     &quot;id_token&quot;: &quot;eyJ...&quot;,        ← who the user is (JWT)
     &quot;token_type&quot;: &quot;Bearer&quot;,
     &quot;expires_in&quot;: 3600
   }
</code></pre>
<p>The <code class="" data-line="">id_token</code> decoded:</p>
<pre><code class="" data-line="">{
  &quot;iss&quot;: &quot;https://idp.corp.com&quot;,          ← issuer (the IdP)
  &quot;sub&quot;: &quot;user-guid-12345&quot;,               ← subject (stable user identifier)
  &quot;aud&quot;: &quot;myapp&quot;,                          ← audience (your client_id)
  &quot;exp&quot;: 1745730000,                       ← expiry (Unix timestamp)
  &quot;iat&quot;: 1745726400,                       ← issued at
  &quot;email&quot;: &quot;vamshi@corp.com&quot;,
  &quot;name&quot;: &quot;Vamshi Krishna&quot;,
  &quot;groups&quot;: [&quot;engineers&quot;, &quot;sre-team&quot;]     ← custom claims from IdP
}
</code></pre>
<pre><code class="" data-line=""># Decode any JWT at the command line (no verification — for debugging only)
echo &quot;eyJ...&quot; | cut -d. -f2 | base64 -d 2&gt;/dev/null | python3 -m json.tool

# Or: jwt.io — paste the token, read every claim
</code></pre>
<p><strong><code class="" data-line="">sub</code> is the stable user identifier.</strong> Email addresses change. Names change. The <code class="" data-line="">sub</code> claim is the IdP&#8217;s internal identifier for the user — use it as the primary key when storing user data. Never store email as the primary key.</p>
<hr />
<h2 id="saml-vs-oidc-when-to-use-which">SAML vs OIDC: When to Use Which</h2>
<table>
<thead>
<tr>
<th></th>
<th>SAML 2.0</th>
<th>OIDC</th>
</tr>
</thead>
<tbody>
<tr>
<td>Format</td>
<td>XML</td>
<td>JSON / JWT</td>
</tr>
<tr>
<td>Transport</td>
<td>HTTP POST (browser only)</td>
<td>HTTP redirect + JSON API</td>
</tr>
<tr>
<td>Age</td>
<td>2002</td>
<td>2014</td>
</tr>
<tr>
<td>Enterprise adoption</td>
<td>Very high (AD FS, Okta, Entra ID)</td>
<td>Very high (newer apps)</td>
</tr>
<tr>
<td>API-friendly</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Mobile apps</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Complexity</td>
<td>High (XML, schemas, signatures)</td>
<td>Medium (JWT, JSON)</td>
</tr>
<tr>
<td>Single Logout</td>
<td>Specified (rarely works well)</td>
<td>Optional, inconsistent</td>
</tr>
</tbody>
</table>
<p><strong>Use SAML when:</strong> You&#8217;re integrating with an enterprise SaaS that only supports SAML (Salesforce classic, legacy HR systems), or your IdP team mandates it.</p>
<p><strong>Use OIDC when:</strong> You&#8217;re building a new application, integrating with a modern IdP, or need API-based token validation. OIDC is the default for everything new.</p>
<p><strong>Use OAuth2 (Client Credentials) when:</strong> Service-to-service authentication with no user — your CI/CD pipeline authenticating to an API, your microservice calling another microservice.</p>
<hr />
<h2 id="a-complete-browser-sso-flow-oidc">A Complete Browser SSO Flow (OIDC)</h2>
<pre><code class="" data-line="">1. User visits https://app.corp.com (not logged in)
   App: no session → redirect to IdP

2. GET https://idp.corp.com/authorize?
        response_type=code
        &amp;client_id=app-corp
        &amp;scope=openid email
        &amp;redirect_uri=https://app.corp.com/callback
        &amp;state=abc123
        &amp;nonce=xyz789

3. IdP: user is not authenticated → show login form
   User: enters vamshi@corp.com + password
   (or: IdP sees existing session cookie → skip login)

4. IdP: authentication success
   Redirect: GET https://app.corp.com/callback?code=AUTH_CODE&amp;state=abc123

5. App (server-side): validate state=abc123 (CSRF protection)
   POST https://idp.corp.com/token
     grant_type=authorization_code
     &amp;code=AUTH_CODE
     &amp;client_id=app-corp
     &amp;client_secret=SECRET
     &amp;redirect_uri=https://app.corp.com/callback

6. IdP responds:
   { &quot;id_token&quot;: &quot;JWT...&quot;, &quot;access_token&quot;: &quot;JWT...&quot;, &quot;expires_in&quot;: 3600 }

7. App: validate id_token signature (using IdP&#039;s JWKS endpoint)
   App: extract sub, email, groups from id_token
   App: create session for vamshi@corp.com
   App: redirect user to original destination
</code></pre>
<p>Step 7 is where most bugs live. The app must validate: signature (using IdP&#8217;s public keys from <code class="" data-line="">/.well-known/jwks.json</code>), <code class="" data-line="">iss</code> (matches the expected IdP), <code class="" data-line="">aud</code> (matches the client_id), <code class="" data-line="">exp</code> (not expired), and <code class="" data-line="">nonce</code> (matches what was sent in step 2). Skip any of these and you have an authentication bypass.</p>
<hr />
<h2 id="common-misconceptions"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Common Misconceptions</h2>
<p><strong>&#8220;OAuth2 is for login.&#8221;</strong> OAuth2 is for authorization delegation. It can be used as a login mechanism only when OIDC (the <code class="" data-line="">openid</code> scope + <code class="" data-line="">id_token</code>) is added on top. &#8220;Login with Google&#8221; uses OIDC, not bare OAuth2.</p>
<p><strong>&#8220;JWTs are encrypted.&#8221;</strong> By default, JWTs are signed (JWS), not encrypted. The header and payload are base64url-encoded — anyone can decode them. Encryption (JWE) is a separate, less commonly used spec. Never put secrets in a JWT payload assuming it&#8217;s private.</p>
<p><strong>&#8220;SAML Single Logout works reliably.&#8221;</strong> SAML SLO is specified but inconsistently implemented. Many SPs ignore SLO requests or don&#8217;t propagate them correctly. Don&#8217;t depend on SLO for security — session revocation requires additional mechanisms (short-lived tokens, token introspection, session registries).</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Relevance</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP Domain 5: Identity and Access Management</td>
<td>SAML, OAuth2, and OIDC are the three protocols that enable federated identity and SSO — understanding which does what is foundational to modern IAM design</td>
</tr>
<tr>
<td>CISSP Domain 4: Communications and Network Security</td>
<td>JWT validation (signature, claims, expiry) is a network security control — failing to validate any claim is an authentication bypass vulnerability</td>
</tr>
<tr>
<td>CISSP Domain 3: Security Architecture and Engineering</td>
<td>The choice of SAML vs OIDC is an architectural decision that affects every application integration, mobile support, and API design</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>SAML 2.0: XML-based browser SSO — three redirects, signed assertion, enterprise legacy apps</li>
<li>OAuth2: authorization delegation — access tokens grant scopes, not identity</li>
<li>OIDC: OAuth2 + <code class="" data-line="">id_token</code> — adds who the user is on top of what they can do</li>
<li><code class="" data-line="">sub</code> is the stable user identifier in OIDC — never use email as a primary key</li>
<li>JWT validation must check: signature, <code class="" data-line="">iss</code>, <code class="" data-line="">aud</code>, <code class="" data-line="">exp</code>, <code class="" data-line="">nonce</code> — missing any is a security bypass</li>
<li>New applications: OIDC. Legacy enterprise SaaS: SAML. Service-to-service: OAuth2 Client Credentials</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP10 covered the protocols. EP11 covers the systems that implement them — the identity providers: what Okta, Entra ID, Keycloak, and AD FS actually do, how they federate with each other, and how SCIM handles user provisioning separately from authentication.</p>
<p><em>Next: <a href="/identity-providers-explained/">Identity Providers Explained: On-Prem, Cloud, SCIM, and Federation</a></em></p>
<p>Get EP11 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fsaml-vs-oidc-vs-oauth2%2F&amp;linkname=SAML%20vs%20OIDC%20vs%20OAuth2%3A%20Which%20Protocol%20Handles%20Which%20Identity%20Problem" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fsaml-vs-oidc-vs-oauth2%2F&amp;linkname=SAML%20vs%20OIDC%20vs%20OAuth2%3A%20Which%20Protocol%20Handles%20Which%20Identity%20Problem" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fsaml-vs-oidc-vs-oauth2%2F&amp;linkname=SAML%20vs%20OIDC%20vs%20OAuth2%3A%20Which%20Protocol%20Handles%20Which%20Identity%20Problem" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fsaml-vs-oidc-vs-oauth2%2F&amp;linkname=SAML%20vs%20OIDC%20vs%20OAuth2%3A%20Which%20Protocol%20Handles%20Which%20Identity%20Problem" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fsaml-vs-oidc-vs-oauth2%2F&amp;linkname=SAML%20vs%20OIDC%20vs%20OAuth2%3A%20Which%20Protocol%20Handles%20Which%20Identity%20Problem" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fsaml-vs-oidc-vs-oauth2%2F&amp;linkname=SAML%20vs%20OIDC%20vs%20OAuth2%3A%20Which%20Protocol%20Handles%20Which%20Identity%20Problem" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fsaml-vs-oidc-vs-oauth2%2F&amp;linkname=SAML%20vs%20OIDC%20vs%20OAuth2%3A%20Which%20Protocol%20Handles%20Which%20Identity%20Problem" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fsaml-vs-oidc-vs-oauth2%2F&#038;title=SAML%20vs%20OIDC%20vs%20OAuth2%3A%20Which%20Protocol%20Handles%20Which%20Identity%20Problem" data-a2a-url="https://linuxcent.com/saml-vs-oidc-vs-oauth2/" data-a2a-title="SAML vs OIDC vs OAuth2: Which Protocol Handles Which Identity Problem"></a></p><p>The post <a href="https://linuxcent.com/saml-vs-oidc-vs-oauth2/">SAML vs OIDC vs OAuth2: Which Protocol Handles Which Identity Problem</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/saml-vs-oidc-vs-oauth2/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1799</post-id>	</item>
		<item>
		<title>SAML vs OIDC: Which Federation Protocol Belongs in Your Cloud?</title>
		<link>https://linuxcent.com/cloud-identity-federation-oidc-saml/</link>
					<comments>https://linuxcent.com/cloud-identity-federation-oidc-saml/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Mon, 20 Apr 2026 17:06:08 +0000</pubDate>
				<category><![CDATA[Cloud IAM]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[Identity Federation]]></category>
		<category><![CDATA[OAuth2]]></category>
		<category><![CDATA[OIDC]]></category>
		<category><![CDATA[SAML]]></category>
		<category><![CDATA[Single Sign-On]]></category>
		<category><![CDATA[Zero Trust]]></category>
		<guid isPermaLink="false">https://linuxcent.com/cloud-identity-federation-oidc-saml/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 10</span> <span class="rt-label rt-postfix">minutes</span></span>How SAML 2.0 and OIDC federation work across cloud providers — token structure, trust chains, conditional access, and building credential-free cross-system identity.</p>
<p>The post <a href="https://linuxcent.com/cloud-identity-federation-oidc-saml/">SAML vs OIDC: Which Federation Protocol Belongs in Your Cloud?</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 10</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<hr />
<p><a href="/what-is-cloud-iam/">What Is Cloud IAM</a> → <a href="/authentication-vs-authorization-iam/">Authentication vs Authorization</a> → <a href="/iam-roles-policies-permissions-explained/">IAM Roles vs Policies</a> → <a href="/aws-iam-deep-dive/">AWS IAM Deep Dive</a> → <a href="/gcp-iam-deep-dive/">GCP Resource Hierarchy IAM</a> → <a href="/azure-rbac-entra-id-guide/">Azure RBAC Scopes</a> → <a href="/workload-identity-oidc-service-accounts/">OIDC Workload Identity</a> → <a href="/cloud-iam-privilege-escalation/">AWS IAM Privilege Escalation</a> → <a href="/iam-least-privilege-audit/">AWS Least Privilege Audit</a> → <strong>SAML vs OIDC Federation</strong></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Federation means downstream systems trust the IdP&#8217;s signed assertion — they never see credentials and don&#8217;t manage them independently</li>
<li>SAML is XML-based, browser-oriented, the enterprise standard; OIDC is JWT-based, API-native, the modern protocol for workload identity and consumer SSO</li>
<li>In OIDC trust policies, the <code class="" data-line="">sub</code> condition is the security boundary — omitting it means any GitHub Actions workflow in any repository can assume your role</li>
<li>Validate all JWT claims: signature, <code class="" data-line="">iss</code>, <code class="" data-line="">aud</code>, <code class="" data-line="">exp</code>, <code class="" data-line="">sub</code> — libraries do this, but need correct configuration (especially <code class="" data-line="">aud</code>)</li>
<li>The IdP is the trust anchor: compromise the IdP and every downstream system is compromised. Treat IdP admin access with the same controls as your most sensitive system.</li>
<li>JIT provisioning and Conditional Access extend federation from &#8220;who are you&#8221; to &#8220;are you in an appropriate context right now&#8221;</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">  FEDERATION: HOW TRUST FLOWS FROM IdP TO DOWNSTREAM SYSTEMS

  Identity Provider  (Okta / Entra ID / Google / AD FS)
  ┌──────────────────────────────────────────────────────────────────┐
  │  User or workload authenticates → IdP issues signed assertion   │
  │                                                                  │
  │  ┌──────────────────────────┐  ┌───────────────────────────┐   │
  │  │  SAML Assertion (XML)    │  │  OIDC ID Token (JWT)       │   │
  │  │  RSA-signed, 5–10 min    │  │  RS256-signed, ~1 hr      │   │
  │  │  Audience: SP entity ID  │  │  aud: client ID           │   │
  │  │  Subject: user identity  │  │  sub: specific workload   │   │
  │  └───────────┬──────────────┘  └──────────┬────────────────┘   │
  └─────────────────────────────────────────────────────────────────┘
                 │  human SSO                  │  workload identity
                 ▼                             ▼
  ┌─────────────────────────┐  ┌───────────────────────────────────┐
  │ SP validates signature  │  │ AWS STS / GCP STS validates       │
  │ + audience + timestamp  │  │ signature + iss + aud + sub       │
  │ → console session       │  │ → AssumeRoleWithWebIdentity       │
  └─────────────────────────┘  └───────────────────────────────────┘

  Security bound: IdP security bounds every system that trusts it
  Disable in Okta → access revoked everywhere that trusts Okta
</code></pre>
<hr />
<h2 id="introduction">Introduction</h2>
<p>Before federation existed, every system had its own user database. Your Jira account. Your AWS account. Your Salesforce account. Your internal wiki. Each one had its own password, its own MFA, its own offboarding process. When an engineer joined, someone had to create accounts in every system. When they left, you hoped whoever processed the offboarding remembered to deactivate all of them.</p>
<p>I&#8217;ve done that audit — the one where you&#8217;re trying to figure out if a former employee still has access to anything. You go system by system, cross-reference against HR records, find accounts that exist in places you&#8217;ve forgotten the company even uses. In one environment I found an ex-engineer&#8217;s account still active in a vendor portal six months after they left, because that system was set up by someone who had since also left the company, and nobody had documented it.</p>
<p>Federation solves this structurally. One identity provider. One place to authenticate. One place to revoke. Every downstream system trusts the IdP&#8217;s assertion rather than managing credentials independently. Disable someone in Okta and they lose access everywhere that trusts Okta — immediately, without a checklist.</p>
<p>This episode is how federation actually works at the protocol level, because understanding the mechanism is what lets you design it securely. A federation setup with a trust policy that accepts assertions from any OIDC issuer is worse than no federation — it&#8217;s a false sense of security.</p>
<hr />
<h2 id="the-federation-model">The Federation Model</h2>
<pre><code class="" data-line="">Identity Provider (IdP)          Service Provider (SP) / Relying Party
  (Okta, Google, AD FS, Entra ID)       (AWS, Salesforce, GitHub, your app)
         │                                          │
         │  1. User authenticates to IdP             │
         │     (password + MFA)                      │
         │                                          │
         │  2. IdP generates a signed assertion      │
         │     (SAML response or OIDC ID Token)      │
         │ ──────────────────────────────────────── &#x25b6;│
         │                                          │
         │  3. SP validates the signature            │
         │     (using IdP&#039;s public certificate       │
         │      or JWKS endpoint)                    │
         │  4. SP maps identity to local permissions │
         │  5. SP grants access                      │
</code></pre>
<p>The SP never sees the user&#8217;s password. It never has one. It trusts the IdP&#8217;s cryptographic signature — if the assertion is signed with the IdP&#8217;s private key, and the SP trusts that key, the identity is accepted.</p>
<p>This trust chain has one critical property: <strong>the security of every SP is bounded by the security of the IdP</strong>. Compromise the IdP, and every system that trusts it is compromised. This is why IdP security deserves the same attention as the most sensitive system it gates access to.</p>
<hr />
<h2 id="saml-20-the-enterprise-standard">SAML 2.0 — The Enterprise Standard</h2>
<p>SAML (Security Assertion Markup Language) is XML-based, verbose, and battle-tested. Published in 2005, it&#8217;s the protocol behind most enterprise SSO deployments. When your company says &#8220;use your corporate login for this vendor app,&#8221; SAML is usually the mechanism.</p>
<h3 id="how-a-saml-login-flows">How a SAML Login Flows</h3>
<pre><code class="" data-line="">1. User visits AWS console (the Service Provider)
2. AWS checks: no active session → redirect to IdP
   → https://company.okta.com/saml?SAMLRequest=...
3. Okta authenticates the user (password, MFA)
4. Okta generates a SAML Assertion — a signed XML document containing:
   - Who the user is (Subject, typically email)
   - Their attributes (group memberships, custom attributes)
   - When the assertion was issued and when it expires (valid 5-10 minutes typically)
   - Which SP this is for (Audience restriction)
   - Okta&#039;s digital signature (RSA-SHA256 or similar)
5. Browser POSTs the assertion to AWS&#039;s ACS (Assertion Consumer Service) URL
6. AWS validates the signature against Okta&#039;s public cert (retrieved from Okta&#039;s metadata URL)
7. AWS reads the SAML attribute for the IAM role
8. AWS calls sts:AssumeRoleWithSAML → issues temporary credentials
9. User gets a console session — no AWS credentials were ever stored anywhere
</code></pre>
<h3 id="what-a-saml-assertion-actually-looks-like">What a SAML Assertion Actually Looks Like</h3>
<pre><code class="" data-line="">&lt;saml:Assertion&gt;
  &lt;saml:Issuer&gt;https://okta.company.com&lt;/saml:Issuer&gt;

  &lt;saml:Subject&gt;
    &lt;saml:NameID&gt;alice@company.com&lt;/saml:NameID&gt;
  &lt;/saml:Subject&gt;

  &lt;saml:AttributeStatement&gt;
    &lt;!-- This attribute tells AWS which IAM role to assume --&gt;
    &lt;saml:Attribute Name=&quot;https://aws.amazon.com/SAML/Attributes/Role&quot;&gt;
      &lt;saml:AttributeValue&gt;
        arn:aws:iam::123456789012:role/EngineerRole,arn:aws:iam::123456789012:saml-provider/OktaProvider
      &lt;/saml:AttributeValue&gt;
    &lt;/saml:Attribute&gt;
  &lt;/saml:AttributeStatement&gt;

  &lt;!-- Critical: time bounds on this assertion --&gt;
  &lt;saml:Conditions NotBefore=&quot;2026-04-11T09:00:00Z&quot; NotOnOrAfter=&quot;2026-04-11T09:05:00Z&quot;&gt;
    &lt;saml:AudienceRestriction&gt;
      &lt;!-- Critical: this assertion is ONLY valid for AWS --&gt;
      &lt;saml:Audience&gt;https://signin.aws.amazon.com/saml&lt;/saml:Audience&gt;
    &lt;/saml:AudienceRestriction&gt;
  &lt;/saml:Conditions&gt;

  &lt;ds:Signature&gt;... RSA-SHA256 signature over the above ...&lt;/ds:Signature&gt;
&lt;/saml:Assertion&gt;
</code></pre>
<p>The <code class="" data-line="">Audience</code> restriction and the <code class="" data-line="">NotOnOrAfter</code> timestamp are two of the most security-critical fields. The audience ensures this assertion can&#8217;t be reused for a different SP. The timestamp ensures it can&#8217;t be replayed after expiry.</p>
<h3 id="setting-up-saml-federation-with-aws">Setting Up SAML Federation with AWS</h3>
<pre><code class="" data-line=""># Register Okta as a SAML provider in AWS IAM
aws iam create-saml-provider \
  --saml-metadata-document file://okta-metadata.xml \
  --name OktaProvider

# Create the IAM role that federated users will assume
aws iam create-role \
  --role-name EngineerRole \
  --assume-role-policy-document &#039;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [{
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;Federated&quot;: &quot;arn:aws:iam::123456789012:saml-provider/OktaProvider&quot;
      },
      &quot;Action&quot;: &quot;sts:AssumeRoleWithSAML&quot;,
      &quot;Condition&quot;: {
        &quot;StringEquals&quot;: {
          &quot;SAML:aud&quot;: &quot;https://signin.aws.amazon.com/saml&quot;
        }
      }
    }]
  }&#039;

# In Okta: configure the AWS IAM Identity Center app
# Attribute mapping: https://aws.amazon.com/SAML/Attributes/Role
# Value: arn:aws:iam::123456789012:role/EngineerRole,arn:aws:iam::123456789012:saml-provider/OktaProvider

# Set maximum session duration (8 hours is reasonable for human access)
aws iam update-role \
  --role-name EngineerRole \
  --max-session-duration 28800
</code></pre>
<h3 id="saml-attack-surface">SAML Attack Surface</h3>
<table>
<thead>
<tr>
<th>Attack</th>
<th>What It Does</th>
<th>Why It Works</th>
<th>Prevention</th>
</tr>
</thead>
<tbody>
<tr>
<td>XML Signature Wrapping (XSW)</td>
<td>Attacker inserts a malicious assertion, wraps it around the legitimate signed one; some SPs validate the wrong element</td>
<td>SAML&#8217;s XML structure is complex; naive signature validation checks the signed element, not the element the SP reads</td>
<td>Use a vetted SAML library — never hand-roll parsing</td>
</tr>
<tr>
<td>Assertion replay</td>
<td>Steal a valid assertion (e.g., via network intercept) and replay it before <code class="" data-line="">NotOnOrAfter</code></td>
<td>If the SP doesn&#8217;t track used assertion IDs, the same assertion can be used multiple times</td>
<td>Short expiry; SP tracks seen assertion IDs</td>
</tr>
<tr>
<td>Audience bypass</td>
<td>SP doesn&#8217;t verify the <code class="" data-line="">Audience</code> field</td>
<td>An assertion issued for SP A can be used at SP B</td>
<td>Always validate <code class="" data-line="">Audience</code> matches your SP entity ID</td>
</tr>
</tbody>
</table>
<p>XML Signature Wrapping is the most interesting attack historically — it was how security researchers demonstrated SAML implementations in AWS, Google, and others could be bypassed before vendors patched their libraries. The lesson: SAML is complex enough that rolling your own parser is asking for a vulnerability.</p>
<hr />
<h2 id="openid-connect-oidc-the-modern-protocol">OpenID Connect (OIDC) — The Modern Protocol</h2>
<p>OIDC is JSON-based, REST-native, and designed for the web and API-first world. Built on top of OAuth 2.0, it&#8217;s the protocol behind &#8220;Sign in with Google,&#8221; GitHub&#8217;s OIDC tokens for Actions, and workload identity federation across cloud providers.</p>
<h3 id="token-anatomy">Token Anatomy</h3>
<p>An OIDC ID Token is a JWT — three base64-encoded parts separated by dots:</p>
<pre><code class="" data-line="">Header.Payload.Signature

Header:
{
  &quot;alg&quot;: &quot;RS256&quot;,           ← signing algorithm
  &quot;kid&quot;: &quot;key-id-123&quot;       ← which key signed this (for JWKS rotation)
}

Payload (the claims):
{
  &quot;iss&quot;: &quot;https://accounts.google.com&quot;,         ← who issued this token
  &quot;sub&quot;: &quot;108378629573454321234&quot;,               ← stable user identifier (not email)
  &quot;aud&quot;: &quot;my-app-client-id&quot;,                   ← who this token is for
  &quot;exp&quot;: 1749600000,                           ← expires at (Unix timestamp)
  &quot;iat&quot;: 1749596400,                           ← issued at
  &quot;email&quot;: &quot;alice@company.com&quot;,
  &quot;email_verified&quot;: true,
  &quot;hd&quot;: &quot;company.com&quot;                          ← hosted domain (Google Workspace)
}

Signature: RSA-SHA256(base64(header) + &quot;.&quot; + base64(payload), idp_private_key)
</code></pre>
<p>The relying party (your application, or AWS STS) validates the signature using the IdP&#8217;s public keys — available at the JWKS endpoint (<code class="" data-line="">/.well-known/jwks.json</code>). The signature verification proves the token was issued by the expected IdP and hasn&#8217;t been tampered with since.</p>
<h3 id="the-full-oidc-token-exchange-github-actions-aws">The Full OIDC Token Exchange (GitHub Actions → AWS)</h3>
<pre><code class="" data-line=""># GitHub Actions automatically provides an OIDC token in the runner environment
# The token contains: iss=token.actions.githubusercontent.com, repo, ref, sha, run_id, etc.

# Step 1: Fetch the OIDC token from GitHub&#039;s token service
TOKEN=$(curl -H &quot;Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN&quot; \
  &quot;$ACTIONS_ID_TOKEN_REQUEST_URL&amp;audience=sts.amazonaws.com&quot; | jq -r &#039;.value&#039;)

# Step 2: Present to AWS STS for exchange
aws sts assume-role-with-web-identity \
  --role-arn arn:aws:iam::123456789012:role/GitHubActionsRole \
  --role-session-name github-deploy \
  --web-identity-token &quot;${TOKEN}&quot;

# STS performs these validations:
# 1. Fetch GitHub&#039;s JWKS: https://token.actions.githubusercontent.com/.well-known/jwks
# 2. Verify signature is valid
# 3. Verify iss = &quot;token.actions.githubusercontent.com&quot; (matches OIDC provider)
# 4. Verify aud = &quot;sts.amazonaws.com&quot;
# 5. Verify sub matches the trust policy condition
# 6. Verify exp is in the future
</code></pre>
<p>The trust policy condition on the IAM role is what prevents any GitHub repository from assuming this role:</p>
<pre><code class="" data-line="">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [{
    &quot;Effect&quot;: &quot;Allow&quot;,
    &quot;Principal&quot;: {
      &quot;Federated&quot;: &quot;arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com&quot;
    },
    &quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,
    &quot;Condition&quot;: {
      &quot;StringEquals&quot;: {
        &quot;token.actions.githubusercontent.com:aud&quot;: &quot;sts.amazonaws.com&quot;,
        &quot;token.actions.githubusercontent.com:sub&quot;: &quot;repo:my-org/my-repo:ref:refs/heads/main&quot;
      }
    }
  }]
}
</code></pre>
<p>The <code class="" data-line="">sub</code> condition is the security boundary. <code class="" data-line="">repo:my-org/my-repo:ref:refs/heads/main</code> means: only runs triggered from the <code class="" data-line="">main</code> branch of <code class="" data-line="">my-org/my-repo</code> can assume this role. A pull request from a fork, a run from a different repo, or a run from a different branch — all get a different <code class="" data-line="">sub</code> claim and the assumption fails.</p>
<p>I&#8217;ve reviewed trust policies that omit the <code class="" data-line="">sub</code> condition and just check <code class="" data-line="">aud</code>. That means any GitHub Actions workflow — in any repository, owned by anyone — can assume that role. That&#8217;s not a misconfiguration to be theoretical about: public GitHub repositories exist, and they can trigger GitHub Actions.</p>
<h3 id="oidc-validation-checklist">OIDC Validation Checklist</h3>
<p>Every application that validates OIDC tokens must check all of these:</p>
<pre><code class="" data-line="">✓ Signature valid (using IdP&#039;s JWKS endpoint — not a hardcoded key)
✓ iss matches the expected IdP URL
✓ aud matches your application&#039;s client ID (not just &quot;any audience&quot;)
✓ exp is in the future
✓ nbf (not before), if present, is in the past
✓ iat is recent (within your clock skew tolerance)
✓ For workload identity: sub is pinned to the specific workload
</code></pre>
<p>Skipping <code class="" data-line="">aud</code> validation is the most common mistake. A token issued for application A with <code class="" data-line="">aud: app-a-client-id</code> should not be accepted by application B. Without audience validation, any application in your system that can obtain a token for the IdP can reuse it at any other application. Libraries like <code class="" data-line="">python-jose</code> and <code class="" data-line="">jsonwebtoken</code> validate <code class="" data-line="">aud</code> by default — but they need to be configured with the expected audience value.</p>
<hr />
<h2 id="enterprise-federation-patterns">Enterprise Federation Patterns</h2>
<h3 id="multi-account-aws-with-iam-identity-center-okta">Multi-Account AWS with IAM Identity Center + Okta</h3>
<p>The pattern I deploy in every multi-account AWS environment:</p>
<pre><code class="" data-line="">Okta (IdP)
  └── IAM Identity Center
        ├── Account: prod     → Permission Sets: ReadOnly, DevOps
        ├── Account: staging  → Permission Sets: Developer  
        ├── Account: shared   → Permission Sets: NetworkAdmin, SecurityAudit
        └── Account: sandbox  → Permission Sets: Admin (sandbox only)
</code></pre>
<pre><code class="" data-line=""># Engineers access accounts through Identity Center portal
aws configure sso
# Prompts: SSO start URL, region, account, role

aws sso login --profile prod-readonly

# List available accounts and roles (useful for tooling and scripts)
aws sso list-accounts --access-token &quot;${TOKEN}&quot;
aws sso list-account-roles --access-token &quot;${TOKEN}&quot; --account-id &quot;${ACCOUNT_ID}&quot;

# Get temporary credentials for a specific account/role
aws sso get-role-credentials \
  --account-id &quot;${ACCOUNT_ID}&quot; \
  --role-name ReadOnly \
  --access-token &quot;${TOKEN}&quot;
</code></pre>
<p>When an engineer is offboarded from Okta, they lose access to every AWS account immediately. No individual IAM user deletion across 20 accounts. No access key hunting. One action in Okta, complete revocation.</p>
<h3 id="just-in-time-jit-provisioning">Just-in-Time (JIT) Provisioning</h3>
<p>Rather than creating user accounts in every downstream system ahead of time, JIT provisioning creates accounts on first login:</p>
<ol>
<li>User authenticates to IdP</li>
<li>SAML/OIDC assertion includes group memberships and attributes</li>
<li>SP receives assertion, checks if a user account exists for this <code class="" data-line="">sub</code></li>
<li>If not: create the account with attributes from the assertion</li>
<li>Grant access based on group claims</li>
<li>On subsequent logins: update the account&#8217;s attributes if claims changed</li>
</ol>
<p>The security property: when a user is disabled in the IdP, their account in downstream systems becomes inaccessible even if the account object still exists. There&#8217;s nothing to log in with. JIT accounts don&#8217;t survive IdP deletion — they&#8217;re inactive shells that produce no risk.</p>
<hr />
<h2 id="the-idp-is-the-trust-anchor-protect-it-accordingly">The IdP Is the Trust Anchor — Protect It Accordingly</h2>
<p>The entire security of a federated system is bounded by the security of the IdP. If an attacker can log into Okta as an admin, they can issue valid SAML assertions for any user, for any role, to any SP that trusts Okta. Every downstream system is compromised simultaneously.</p>
<p>This is not theoretical. In the 2023 Caesars and MGM Resorts attacks, initial access was achieved through social engineering against identity provider support — not through technical exploitation of cloud infrastructure. Once identity infrastructure is compromised, everything downstream follows.</p>
<p>What this means practically:</p>
<ul>
<li>MFA for all IdP admin accounts — hardware FIDO2 keys, not TOTP. TOTP codes can be phished in real-time. Hardware keys cannot.</li>
<li>PIM / JIT access for IdP configuration changes — no standing admin access</li>
<li>Separate monitoring and alerting for IdP admin activity</li>
<li>Audit who can modify SAML/OIDC configurations and attribute mappings in the IdP — these are the levers for privilege escalation</li>
<li>Narrow audience restrictions — configure which SPs can receive assertions; don&#8217;t create a wildcard IdP configuration that serves all SPs</li>
</ul>
<h3 id="conditional-access-adding-context-to-federation">Conditional Access — Adding Context to Federation</h3>
<p>Modern IdPs support Conditional Access policies that restrict when assertions are issued:</p>
<pre><code class="" data-line="">// Entra ID Conditional Access: require MFA + compliant device for AWS access
{
  &quot;conditions&quot;: {
    &quot;applications&quot;: {
      &quot;includeApplications&quot;: [&quot;AWS-Application-ID-in-Entra&quot;]
    },
    &quot;users&quot;: {
      &quot;includeGroups&quot;: [&quot;all-employees&quot;]
    },
    &quot;locations&quot;: {
      &quot;excludeLocations&quot;: [&quot;NamedLocation-CorporateNetwork&quot;]
    }
  },
  &quot;grantControls&quot;: {
    &quot;operator&quot;: &quot;AND&quot;,
    &quot;builtInControls&quot;: [&quot;mfa&quot;, &quot;compliantDevice&quot;]
  }
}
</code></pre>
<p>This policy: when an employee accesses AWS from outside the corporate network, they must use MFA on a device that MDM has verified as compliant. From inside the network, the policy still applies but the named location exclusion can relax certain requirements.</p>
<p>Conditional Access is how you move beyond &#8220;authenticated to IdP&#8221; as the only gate. Device health, network location, risk score — these become inputs to the access decision.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Framework</th>
<th>Reference</th>
<th>What It Covers Here</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP</td>
<td>Domain 5 — Identity and Access Management</td>
<td>Federation is the mechanism for extending identity trust across organizational boundaries</td>
</tr>
<tr>
<td>CISSP</td>
<td>Domain 3 — Security Architecture</td>
<td>Trust relationships must be explicitly designed; overly broad federation trust is an architectural failure</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.19 Information security in supplier relationships</td>
<td>Federation with third-party IdPs and SPs establishes a cross-organizational trust boundary that must be governed</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>8.5 Secure authentication</td>
<td>SAML and OIDC are the secure authentication protocols for federated access — token validation requirements</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.17 Authentication information</td>
<td>Credential lifecycle in federated systems — no passwords distributed to SPs; IdP manages authentication</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.1</td>
<td>Federated identity is the access control mechanism for human access to cloud environments in CC6.1</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.6</td>
<td>Logical access from outside system boundaries — federation with external IdPs and partner organizations</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Federation means downstream systems trust the IdP&#8217;s signed assertion — they never see credentials and don&#8217;t need to manage them independently</li>
<li>SAML is XML-based, browser-oriented, widely supported for enterprise SSO; OIDC is JWT-based, API-friendly, the protocol for modern workload identity and consumer SSO</li>
<li>In OIDC, the <code class="" data-line="">sub</code> condition in trust policies is what prevents any workload from assuming any role — omitting it is a critical misconfiguration</li>
<li>Validate all JWT claims: signature, <code class="" data-line="">iss</code>, <code class="" data-line="">aud</code>, <code class="" data-line="">exp</code>, <code class="" data-line="">sub</code> — libraries do this, but they need correct configuration</li>
<li>The IdP is the trust anchor — its security posture bounds the security of every system that trusts it. Treat IdP admin access with the same controls as your most sensitive systems.</li>
<li>JIT provisioning and Conditional Access extend federation from &#8220;who are you&#8221; to &#8220;are you in an appropriate context right now&#8221;</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP11 brings this into Kubernetes — RBAC, service account tokens, and how the Kubernetes authorization layer interacts with cloud IAM. Two separate systems, both requiring security. A gap in either becomes a gap in both.</p>
<p><em>Next: <a href="/kubernetes-rbac-cloud-iam/">Kubernetes RBAC and AWS IAM</a></em></p>
<p>Get EP11 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-identity-federation-oidc-saml%2F&amp;linkname=SAML%20vs%20OIDC%3A%20Which%20Federation%20Protocol%20Belongs%20in%20Your%20Cloud%3F" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-identity-federation-oidc-saml%2F&amp;linkname=SAML%20vs%20OIDC%3A%20Which%20Federation%20Protocol%20Belongs%20in%20Your%20Cloud%3F" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-identity-federation-oidc-saml%2F&amp;linkname=SAML%20vs%20OIDC%3A%20Which%20Federation%20Protocol%20Belongs%20in%20Your%20Cloud%3F" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-identity-federation-oidc-saml%2F&amp;linkname=SAML%20vs%20OIDC%3A%20Which%20Federation%20Protocol%20Belongs%20in%20Your%20Cloud%3F" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-identity-federation-oidc-saml%2F&amp;linkname=SAML%20vs%20OIDC%3A%20Which%20Federation%20Protocol%20Belongs%20in%20Your%20Cloud%3F" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-identity-federation-oidc-saml%2F&amp;linkname=SAML%20vs%20OIDC%3A%20Which%20Federation%20Protocol%20Belongs%20in%20Your%20Cloud%3F" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-identity-federation-oidc-saml%2F&amp;linkname=SAML%20vs%20OIDC%3A%20Which%20Federation%20Protocol%20Belongs%20in%20Your%20Cloud%3F" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fcloud-identity-federation-oidc-saml%2F&#038;title=SAML%20vs%20OIDC%3A%20Which%20Federation%20Protocol%20Belongs%20in%20Your%20Cloud%3F" data-a2a-url="https://linuxcent.com/cloud-identity-federation-oidc-saml/" data-a2a-title="SAML vs OIDC: Which Federation Protocol Belongs in Your Cloud?"></a></p><p>The post <a href="https://linuxcent.com/cloud-identity-federation-oidc-saml/">SAML vs OIDC: Which Federation Protocol Belongs in Your Cloud?</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/cloud-identity-federation-oidc-saml/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1510</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-08-22 12:53:45 by W3 Total Cache
-->