Authentication Protocols

"Authentication is the art of proving you are who you claim to be. The difficulty lies in doing so without revealing the secret that proves it." -- Whitfield Diffie

You have probably experienced this: during a login flow, you get redirected through three different URLs before finally landing on the internal dashboard. One URL has your company domain, one is a Microsoft page, one has "adfs" in it, and you end up somewhere else entirely. What is happening?

That is a SAML-based single sign-on flow through Active Directory Federation Services. Each redirect serves a purpose -- each one carries a different cryptographic proof of your identity from one trust domain to the next. This chapter takes you through the major authentication protocols -- Kerberos, LDAP, SAML, RADIUS -- and shows you why enterprise authentication is a carefully choreographed dance.


Kerberos: The Three-Headed Dog

Kerberos is the authentication protocol that guards almost every Windows Active Directory domain and many Unix environments. Named after the three-headed dog from Greek mythology that guards the gates of the underworld, the protocol involves three parties in its authentication dance.

The fundamental insight of Kerberos is that your password should be used exactly once: to prove your identity to a trusted third party. After that, you carry cryptographic tokens (tickets) that prove your identity without ever revealing your password again. No service you access ever sees your password.

The Three Parties

graph TD
    subgraph "Kerberos Architecture"
        Client["CLIENT (You)<br/>Has: username + password<br/>Wants: access to services"]

        subgraph KDC["KEY DISTRIBUTION CENTER (KDC)"]
            AS["Authentication Service (AS)<br/>Verifies your identity<br/>Issues TGT"]
            TGS["Ticket Granting Service (TGS)<br/>Issues service tickets<br/>using your TGT"]
        end

        Service["SERVICE<br/>(File server, database,<br/>web app, etc.)<br/>Has: its own key<br/>shared with KDC"]
    end

    Client -->|"1. AS-REQ: I am arjun@ACME.COM<br/>(pre-auth encrypted with<br/>password-derived key)"| AS
    AS -->|"2. AS-REP: Here is your TGT<br/>(encrypted with KDC's key)<br/>+ session key (encrypted with<br/>your password-derived key)"| Client
    Client -->|"3. TGS-REQ: I need access to HTTP/webapp<br/>(presents TGT + authenticator)"| TGS
    TGS -->|"4. TGS-REP: Here is your service ticket<br/>(encrypted with service's key)<br/>+ session key for the service"| Client
    Client -->|"5. AP-REQ: Here is my service ticket<br/>(service decrypts with its own key)"| Service
    Service -->|"6. AP-REP: Authenticated!<br/>Here is your data"| Client

The Kerberos Dance: Step by Step

sequenceDiagram
    participant C as Client (user)
    participant AS as KDC: Authentication Service
    participant TGS as KDC: Ticket Granting Service
    participant S as Service (HTTP/webapp)

    Note over C,AS: Phase 1: Initial Authentication<br/>(happens once, at login)

    C->>AS: AS-REQ: "I am user@ACME.COM"<br/>Pre-authentication: timestamp<br/>encrypted with key derived<br/>from user's password
    AS->>AS: Look up user in database<br/>Derive key from stored password hash<br/>Decrypt pre-auth timestamp<br/>Verify timestamp is recent (±5 min)
    AS->>C: AS-REP contains:<br/>1. TGT (encrypted with krbtgt key):<br/>   - Client: user@ACME.COM<br/>   - Session key (SK1)<br/>   - Expiry: 10 hours<br/>   - PAC (groups, SID)<br/>2. SK1 encrypted with user's key

    Note over C: Client decrypts SK1 using<br/>password-derived key.<br/>Password is no longer needed.<br/>Client stores TGT + SK1 in<br/>credential cache.

    Note over C,TGS: Phase 2: Getting a Service Ticket<br/>(happens for each new service)

    C->>TGS: TGS-REQ contains:<br/>1. TGT (opaque, cannot read it)<br/>2. Authenticator (encrypted with SK1):<br/>   - Client: user@ACME.COM<br/>   - Timestamp<br/>3. Target: HTTP/webapp.acme.com
    TGS->>TGS: Decrypt TGT with krbtgt key<br/>Extract SK1 from TGT<br/>Decrypt authenticator with SK1<br/>Verify client name matches<br/>Verify timestamp is recent
    TGS->>C: TGS-REP contains:<br/>1. Service Ticket (encrypted with<br/>   webapp's key):<br/>   - Client: user@ACME.COM<br/>   - Session key (SK2)<br/>   - Expiry: 10 hours<br/>   - PAC<br/>2. SK2 encrypted with SK1

    Note over C,S: Phase 3: Accessing the Service

    C->>S: AP-REQ contains:<br/>1. Service Ticket (opaque to client)<br/>2. Authenticator (encrypted with SK2):<br/>   - Client name + timestamp
    S->>S: Decrypt service ticket with own key<br/>Extract SK2<br/>Decrypt authenticator with SK2<br/>Verify client name and timestamp
    S->>C: AP-REP: "Welcome, user"<br/>(optional, proves server identity)

Notice the key insight: your password is only used once, in the first step. Your password derives an encryption key (using a key derivation function like PBKDF2 with the salt being the principal name). That key decrypts the session key in the AS-REP. After that, your password is never used again for the lifetime of the TGT -- typically 10 hours. The TGT acts as a renewable credential. The service you access never sees your password, never sees your password hash, and never even has the ability to impersonate you to other services. Each service ticket is scoped to one specific service.

**Inside the Kerberos Ticket**

A Ticket Granting Ticket (TGT) is an encrypted blob that only the KDC can read. It contains:

- **Client principal name**: e.g., `user@ACME.COM`
- **TGS principal name**: `krbtgt/ACME.COM@ACME.COM`
- **Session key**: A randomly generated symmetric key (SK1) for communication between client and TGS
- **Auth time**: When the user originally authenticated
- **Start time**: When the ticket becomes valid
- **End time**: When the ticket expires (typically 10 hours)
- **Renew till**: Maximum renewable lifetime (typically 7 days)
- **Client addresses**: IP restrictions (optional, rarely used now)
- **Authorization data**: The **Privilege Attribute Certificate (PAC)** in Active Directory environments, containing:
  - User's Security Identifier (SID)
  - SIDs of all groups the user belongs to
  - User account flags (enabled, locked, password expired, etc.)
  - Logon information (logon count, last logon time)

The entire TGT is encrypted with the **krbtgt** account's long-term key. The krbtgt account is the most sensitive account in Active Directory -- its password hash is the master key of the entire Kerberos realm. If compromised, an attacker can forge TGTs for any user with any group membership.

A **service ticket** has a similar structure but is encrypted with the target service's key (derived from the service account's password). The service decrypts it with its own key, reads the client's identity and PAC, and makes authorization decisions.

Kerberos on the Command Line

# List your current Kerberos tickets (credential cache)
klist
# Ticket cache: FILE:/tmp/krb5cc_1000
# Default principal: user@ACME.COM
#
# Valid starting       Expires              Service principal
# 03/12/2026 08:01:00  03/12/2026 18:01:00  krbtgt/ACME.COM@ACME.COM
# 03/12/2026 08:05:00  03/12/2026 18:01:00  HTTP/webapp.acme.com@ACME.COM

# Obtain a TGT interactively
kinit user@ACME.COM
# Password for user@ACME.COM: ****

# Obtain a TGT using a keytab (for service accounts, automated processes)
kinit -kt /etc/krb5.keytab HTTP/webapp.acme.com@ACME.COM

# Request a service ticket (the client does this automatically, but you can force it)
kvno HTTP/webapp.acme.com@ACME.COM

# View detailed ticket information (encryption types, flags)
klist -ef
# Flags: FRIA (Forwardable, Renewable, Initial, pre-Authenticated)
# Etype (skey, tkt): aes256-cts-hmac-sha1-96, aes256-cts-hmac-sha1-96

# Destroy all tickets (log out)
kdestroy

# Kerberos client configuration
cat /etc/krb5.conf
# [libdefaults]
#   default_realm = ACME.COM
#   dns_lookup_realm = false
#   dns_lookup_kdc = true
# [realms]
#   ACME.COM = {
#     kdc = dc01.acme.com
#     kdc = dc02.acme.com
#     admin_server = dc01.acme.com
#   }

Kerberos Attack Vectors

**Kerberoasting: The Most Common Kerberos Attack**

Any authenticated domain user can request a service ticket for any service that has a Service Principal Name (SPN) registered. The service ticket is encrypted with the service account's password hash. This is by design -- it is how Kerberos works. The vulnerability is that the attacker can take the encrypted ticket offline and brute-force the service account's password without generating any further network traffic or authentication failures.

**The attack flow:**

1. Attacker authenticates as any domain user (even a low-privilege user)
2. Requests service tickets for all SPNs in the domain
3. Extracts the encrypted tickets
4. Cracks the tickets offline using hashcat or john

\```bash
# Using Impacket's GetUserSPNs to enumerate SPNs and request tickets
GetUserSPNs.py ACME.COM/user:password -request -outputfile kerberoast.txt

# Crack the tickets offline (no network needed, no detection)
hashcat -m 13100 kerberoast.txt rockyou.txt --rules-file best64.rule

# If the service account password is weak (e.g., "Summer2025!"),
# it cracks in minutes. The attacker now has the service account's
# password and can authenticate as that service.
\```

**Why this matters:** Service accounts often have elevated privileges. A SQL Server service account might be a domain admin. A backup service account might have access to every file share. The service account password is the weakest link.

**Other Kerberos attacks:**

- **Golden Ticket**: If an attacker obtains the krbtgt account's password hash (via DCSync or NTDS.dit extraction), they can forge TGTs for any user, including non-existent users with Domain Admin privileges. The forged ticket is cryptographically valid because it is encrypted with the real krbtgt key. Detection is extremely difficult because the ticket looks legitimate.

- **Silver Ticket**: Forging a service ticket using a service account's password hash. More limited than golden tickets (only works for that specific service) but harder to detect because the forged ticket never touches the KDC.

- **Pass-the-Ticket**: Stealing Kerberos tickets from a compromised machine's memory (using tools like Mimikatz or Rubeus) and reusing them on another machine. No password cracking needed.

- **AS-REP Roasting**: If a user account has Kerberos pre-authentication disabled (a common misconfiguration), anyone can request an AS-REP for that user. The AS-REP contains data encrypted with the user's password hash, which can be cracked offline.

**Mitigations:**
- Use Group Managed Service Accounts (gMSAs) with 120-character auto-rotated passwords -- these are immune to Kerberoasting
- For legacy service accounts, use 30+ character random passwords
- Enable AES encryption (disable RC4/DES) for all Kerberos operations
- Rotate the krbtgt password at least every 180 days (requires rotating twice with a 12+ hour gap)
- Monitor for abnormal TGS-REQ patterns: a single user requesting tickets for many SPNs in a short time is suspicious
- Enable Kerberos event logging (Event IDs 4769 for TGS requests, 4768 for TGT requests)

LDAP: The Directory Service

LDAP (Lightweight Directory Access Protocol) is not strictly an authentication protocol -- it is a directory service protocol for querying and modifying hierarchical data stores. But it is so deeply intertwined with authentication that you cannot understand enterprise identity without it.

LDAP Directory Structure

graph TD
    Root["dc=acme,dc=com<br/>(Domain Root)"]

    Root --> People["ou=People<br/>(Organizational Unit)"]
    Root --> Groups["ou=Groups"]
    Root --> Services["ou=Services"]

    People --> User1["cn=User One<br/>uid=user1<br/>mail=user1@acme.com<br/>userPassword=&#123;SSHA&#125;...<br/>memberOf=cn=developers,ou=Groups<br/>employeeNumber=1042"]
    People --> User2["cn=User Two<br/>uid=user2<br/>mail=user2@acme.com<br/>memberOf=cn=security,ou=Groups<br/>title=Senior Security Engineer"]

    Groups --> Devs["cn=developers<br/>member=cn=User One,..."]
    Groups --> Security["cn=security<br/>member=cn=User Two,..."]
    Groups --> Admins["cn=domain-admins<br/>member=cn=User Two,..."]

    Services --> Webapp["cn=webapp<br/>servicePrincipalName=<br/>HTTP/webapp.acme.com"]

Every entry in LDAP is identified by its Distinguished Name (DN) -- a unique path from the entry to the root of the directory tree. For example:

  • A user's DN: cn=User One,ou=People,dc=acme,dc=com
  • The developers group: cn=developers,ou=Groups,dc=acme,dc=com

LDAP Authentication: Bind Operations

LDAP "authentication" is actually an LDAP bind operation: the client provides a DN and a password, and the server verifies them. There are two common patterns:

Simple Bind -- The client sends the DN and password directly. Without TLS, this is sent in cleartext over the network. Always use LDAPS (LDAP over TLS, port 636) or StartTLS (upgrade on port 389).

SASL Bind -- The client uses a SASL mechanism (GSSAPI for Kerberos, DIGEST-MD5, etc.) for authentication. GSSAPI/Kerberos is the recommended approach in Active Directory environments because it never sends the password over the network.

# LDAP simple bind -- test authentication (ALWAYS use ldaps://)
ldapwhoami -x -H ldaps://ldap.acme.com \
  -D "cn=User One,ou=People,dc=acme,dc=com" \
  -W  # prompts for password
# Output: dn:cn=User One,ou=People,dc=acme,dc=com

# Search for a user
ldapsearch -x -H ldaps://ldap.acme.com \
  -D "cn=admin,dc=acme,dc=com" -W \
  -b "ou=People,dc=acme,dc=com" \
  "(uid=user1)" cn mail memberOf title

# Search for all members of a group
ldapsearch -x -H ldaps://ldap.acme.com \
  -D "cn=admin,dc=acme,dc=com" -W \
  -b "ou=Groups,dc=acme,dc=com" \
  "(cn=developers)" member

# Test LDAP connectivity and discover naming contexts
ldapsearch -x -H ldaps://ldap.acme.com -b "" -s base namingContexts

Application Authentication Flow with LDAP

sequenceDiagram
    participant User as User (Browser)
    participant App as Web Application
    participant LDAP as LDAP Server<br/>(OpenLDAP / Active Directory)

    User->>App: Login form submission<br/>username: user1<br/>password: ****
    App->>App: Construct user DN from username<br/>(or search for DN first)

    alt Direct Bind (Simple)
        App->>LDAP: LDAP Bind<br/>DN: cn=User One,ou=People,dc=acme,dc=com<br/>Password: ****
    else Search-then-Bind (Recommended)
        App->>LDAP: Bind as service account
        LDAP->>App: Bind successful
        App->>LDAP: Search: (uid=user1)<br/>in ou=People,dc=acme,dc=com
        LDAP->>App: Found: cn=User One,ou=People,...
        App->>LDAP: Re-bind as found DN<br/>with user's password
    end

    LDAP->>App: Bind result: Success or Failure

    alt Bind Successful
        App->>LDAP: Search for user attributes:<br/>groups, email, department, title
        LDAP->>App: Attributes returned
        App->>App: Create session with<br/>user info + group memberships
        App->>User: Login successful, session created
    else Bind Failed
        App->>User: Invalid credentials
    end

The "search-then-bind" pattern is recommended because it separates the concerns of finding the user (which may require admin privileges) from verifying the user's password (which uses the user's own credentials). Direct bind requires knowing the user's exact DN format, which may vary.

**LDAP Injection**

Just like SQL injection, LDAP search filters are vulnerable to injection if user input is not sanitized:

\```
# Vulnerable filter construction (Python pseudocode)
filter = f"(&(uid={user_input})(userPassword={pass_input}))"

# Attack: user_input = "*)(objectClass=*"
# Resulting filter: (&(uid=*)(objectClass=*)(userPassword=anything))
# This bypasses authentication by matching all objects
\```

**Prevention:**
- Always use parameterized LDAP queries or properly escape special characters: `*`, `(`, `)`, `\`, NUL
- Most LDAP libraries provide built-in escaping functions (e.g., Python's `ldap3.utils.dn.escape_rdn` or `ldap.filter.escape_filter_chars`)
- Use the search-then-bind pattern: search for the user DN with a service account (using escaped input), then bind as that DN with the user's password (bind does not use filter syntax, so it is not injectable)

Active Directory: LDAP + Kerberos + More

Active Directory is Microsoft's directory service that combines LDAP, Kerberos, DNS, Group Policy, and certificate services into a unified enterprise identity platform. It is the operating system for enterprise identity. Understanding AD means understanding how all these protocols work together.

Active Directory Architecture

graph TD
    subgraph Forest["AD Forest: acme.com"]
        subgraph Domain1["Domain: acme.com"]
            DC1["Domain Controller 1<br/>(dc01.acme.com)<br/>Kerberos KDC<br/>LDAP Directory (NTDS.dit)<br/>DNS Server<br/>Group Policy storage"]
            DC2["Domain Controller 2<br/>(dc02.acme.com)<br/>Replica of DC1<br/>Provides redundancy"]
            DC1 <-->|"Multi-master<br/>replication"| DC2
        end

        subgraph Domain2["Child Domain: eu.acme.com"]
            DC3["Domain Controller<br/>(dc01.eu.acme.com)"]
        end

        Domain1 <-->|"Trust relationship<br/>(bidirectional, transitive)"| Domain2
    end

    Clients["Windows Workstations<br/>& Servers"] -->|"Kerberos auth<br/>LDAP queries<br/>DNS lookups<br/>GPO application"| DC1
    Clients -->|"Failover"| DC2

Key Active Directory concepts:

  • Forest: The top-level security boundary. Cross-forest trust requires explicit configuration. Forests do not trust each other by default.
  • Domain: An administrative boundary within a forest. acme.com and eu.acme.com are separate domains in the same forest. Domains within a forest automatically trust each other (transitive trust).
  • NTDS.dit: The Active Directory database file on each domain controller. Contains all user accounts, password hashes, group memberships, and GPO data. This file is the primary target for attackers because it contains everything needed to impersonate any user.
  • Group Policy Objects (GPOs): Configuration policies pushed to domain-joined machines. GPOs control password policies, software installation, security settings, firewall rules, and thousands of other settings.
# Query Active Directory from Linux
# Find domain controllers via DNS SRV records
dig _ldap._tcp.dc._msdcs.acme.com SRV
# ;; ANSWER SECTION:
# _ldap._tcp.dc._msdcs.acme.com. 600 IN SRV 0 100 389 dc01.acme.com.
# _ldap._tcp.dc._msdcs.acme.com. 600 IN SRV 0 100 389 dc02.acme.com.

# Search AD for a user (using AD-specific attribute names)
ldapsearch -x -H ldaps://dc01.acme.com \
  -D "user@acme.com" -W \
  -b "dc=acme,dc=com" \
  "(sAMAccountName=user1)" displayName memberOf userAccountControl

# Test Kerberos authentication against AD
kinit user@ACME.COM
klist  # Should show krbtgt/ACME.COM ticket
**NTLM vs. Kerberos in Active Directory**

Active Directory supports two authentication protocols, and understanding when each is used is critical for security:

**Kerberos** (preferred, should be used everywhere possible):
- Ticket-based: password never sent over the network
- Supports mutual authentication (client verifies server, server verifies client)
- Used when connecting by **hostname** within a domain (e.g., `\\fileserver.acme.com\share`)
- Supports delegation (constrained and resource-based constrained delegation)
- Faster after initial TGT: no DC contact needed for cached service tickets

**NTLM** (legacy, still widespread, security risk):
- Challenge-response protocol: server sends a challenge, client responds with hash-based proof
- Used when connecting by **IP address** (e.g., `\\10.0.1.50\share`) or to non-domain systems
- Every authentication requires a round-trip to the domain controller
- Vulnerable to **relay attacks**: an attacker intercepts an NTLM authentication and replays it to another server in real-time
- Vulnerable to **pass-the-hash**: an attacker who obtains the NTLM hash can authenticate without knowing the password
- Does not support mutual authentication

**NTLM relay attacks** are among the most common and dangerous attacks in Active Directory environments. The attacker positions themselves between a client and a server, intercepts the NTLM challenge-response, and relays it to a different server where the victim has privileges. Tools like ntlmrelayx (from Impacket) automate this entirely.

**Recommendation:** Disable NTLM where possible. Audit NTLM usage with `Audit NTLM Authentication` Group Policy settings. Microsoft has announced plans to deprecate NTLM, but the transition will take years because legacy applications depend on it.

SAML: Enterprise Single Sign-On

SAML (Security Assertion Markup Language) is an XML-based protocol for exchanging authentication and authorization data between parties. It is the backbone of enterprise SSO, enabling users to authenticate once and access dozens of cloud and on-premises applications without re-entering credentials.

The SAML SP-Initiated Login Flow

sequenceDiagram
    participant User as User (Browser)
    participant SP as Service Provider<br/>(Salesforce, Slack, AWS)
    participant IdP as Identity Provider<br/>(Okta, Azure AD, ADFS)

    User->>SP: 1. Access application<br/>(e.g., salesforce.com/dashboard)
    SP->>SP: 2. User not authenticated.<br/>Generate SAML AuthnRequest<br/>(ID, issuer, ACS URL, timestamp)
    SP->>User: 3. HTTP 302 Redirect to IdP<br/>with SAML AuthnRequest<br/>(base64-encoded, in URL query param<br/>or POST body)
    User->>IdP: 4. Browser follows redirect<br/>to IdP login page

    alt User has active IdP session
        IdP->>IdP: Session valid, skip login
    else No active session
        IdP->>User: 5. Login page
        User->>IdP: 6. Authenticate<br/>(username + password + MFA)
        IdP->>IdP: 7. Verify credentials<br/>against AD/LDAP
    end

    IdP->>IdP: 8. Build SAML Response:<br/>Assertion with user identity<br/>Attributes (email, groups)<br/>Conditions (audience, time)<br/>Sign with IdP private key
    IdP->>User: 9. HTML form with auto-submit<br/>POST to SP's ACS URL<br/>containing base64 SAML Response
    User->>SP: 10. Browser POSTs SAML Response<br/>to Assertion Consumer Service URL

    SP->>SP: 11. Validate SAML Response:<br/>a) Verify XML signature (IdP cert)<br/>b) Check assertion not expired<br/>c) Check audience matches our entity ID<br/>d) Check InResponseTo matches our request<br/>e) Extract user identity + attributes
    SP->>User: 12. Session created!<br/>User is logged in to the application

What Is Inside a SAML Assertion

A SAML Response contains an Assertion, which is the core identity claim. Here is an annotated example:

<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
  ID="_assertion_abc123"
  IssueInstant="2026-03-12T10:00:00Z"
  Version="2.0">

  <!-- WHO ISSUED THIS ASSERTION -->
  <saml:Issuer>https://idp.acme.com/saml</saml:Issuer>

  <!-- CRYPTOGRAPHIC SIGNATURE (by the IdP) -->
  <!-- The SP verifies this using the IdP's public key/certificate -->
  <!-- This is what makes the assertion trustworthy -->
  <ds:Signature>
    <ds:SignedInfo>
      <ds:Reference URI="#_assertion_abc123">
        <!-- Canonicalization, digest algorithm, digest value -->
      </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue>base64-encoded-signature...</ds:SignatureValue>
  </ds:Signature>

  <!-- WHO IS THIS ASSERTION ABOUT -->
  <saml:Subject>
    <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
      user@acme.com
    </saml:NameID>
    <!-- BEARER CONFIRMATION: proves the browser is the legitimate bearer -->
    <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
      <saml:SubjectConfirmationData
        InResponseTo="_request_xyz789"
        Recipient="https://salesforce.com/acs"
        NotOnOrAfter="2026-03-12T10:05:00Z"/>
    </saml:SubjectConfirmation>
  </saml:Subject>

  <!-- TIME AND AUDIENCE RESTRICTIONS -->
  <saml:Conditions NotBefore="2026-03-12T10:00:00Z"
                   NotOnOrAfter="2026-03-12T10:05:00Z">
    <!-- This assertion is only valid for THIS specific SP -->
    <saml:AudienceRestriction>
      <saml:Audience>https://salesforce.com</saml:Audience>
    </saml:AudienceRestriction>
  </saml:Conditions>

  <!-- HOW THE USER AUTHENTICATED -->
  <saml:AuthnStatement AuthnInstant="2026-03-12T10:00:00Z"
                       SessionIndex="_session_def456">
    <saml:AuthnContext>
      <saml:AuthnContextClassRef>
        urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
      </saml:AuthnContextClassRef>
    </saml:AuthnContext>
  </saml:AuthnStatement>

  <!-- USER ATTRIBUTES (authorization data) -->
  <saml:AttributeStatement>
    <saml:Attribute Name="email">
      <saml:AttributeValue>user@acme.com</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute Name="groups">
      <saml:AttributeValue>developers</saml:AttributeValue>
      <saml:AttributeValue>backend-team</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute Name="department">
      <saml:AttributeValue>Engineering</saml:AttributeValue>
    </saml:Attribute>
  </saml:AttributeStatement>

</saml:Assertion>

Yes, that is a lot of XML. SAML was designed in the early 2000s when XML was the dominant data interchange format. It is verbose, complex, and the XML signature validation is surprisingly difficult to implement correctly. But it works, it is battle-tested in production across millions of enterprise SSO deployments, and it is everywhere. Nearly every enterprise SaaS application supports SAML.

**SAML Security Pitfalls**

SAML implementations have been the source of numerous critical vulnerabilities:

1. **XML Signature Wrapping (XSW)**: The attacker modifies the assertion content while keeping the original signed XML element intact in a different location in the document. The SP verifies the signature on the original (valid) element but processes the attacker's modified element for authorization. This attack has affected major SAML libraries including Ruby's ruby-saml, Python's pysaml2, and others.

2. **Assertion replay**: Without proper validation of `InResponseTo` (linking the assertion to the original request) and `NotOnOrAfter` timestamps, an intercepted assertion can be replayed to gain access.

3. **Missing audience validation**: If the SP does not verify the `Audience` field, an assertion intended for one SP can be used at a different SP. The attacker obtains a valid assertion for App A and presents it to App B.

4. **XML comment injection**: Some XML parsers handle comments in ways that alter the parsed value. `user@acme.com` with a strategically placed XML comment like `user@acme.com<!---->` can be parsed differently by the signature verification code and the application code, allowing identity spoofing.

5. **Signature exclusion**: If the SP does not enforce that the assertion is signed, an attacker can submit an unsigned (and therefore forged) assertion.

**Golden rule:** Never implement SAML parsing yourself. Use a well-maintained, security-audited SAML library. The XML attack surface is enormous and subtle.
Decode a SAML response to understand what your IdP is sending:

\```bash
# Capture a SAML response from browser dev tools:
# 1. Open Network tab in dev tools
# 2. Log in through SSO
# 3. Find the POST request to the ACS URL
# 4. Copy the SAMLResponse parameter value

# Decode it (base64 -> XML)
echo "PHNhbWxwOlJlc3BvbnNl..." | base64 -d | xmllint --format -

# For quick inspection, use an online tool:
# https://www.samltool.com/decode.php
# (ONLY for non-production, test data!)
\```

Examine the decoded response:
- Is the assertion signed? (Look for `<ds:Signature>`)
- Are conditions present? (`NotBefore`, `NotOnOrAfter`, `AudienceRestriction`)
- What attributes does the IdP include?
- Is the `InResponseTo` field set?

RADIUS: Network Access Authentication

RADIUS (Remote Authentication Dial-In User Service) is the protocol that controls who gets on the network in the first place. When you connect to corporate Wi-Fi, plug into an Ethernet port, or connect to a VPN, RADIUS is usually the system making the authentication and authorization decision.

The RADIUS Authentication Flow

sequenceDiagram
    participant User as User Device<br/>(Supplicant)
    participant NAS as Network Access Server<br/>(Wi-Fi AP / Switch / VPN)
    participant RADIUS as RADIUS Server<br/>(FreeRADIUS / Cisco ISE / NPS)
    participant LDAP as Identity Store<br/>(Active Directory / LDAP)

    User->>NAS: 1. Connect to network<br/>(associate with Wi-Fi AP)
    NAS->>User: 2. EAP Identity Request<br/>("Who are you?")
    User->>NAS: 3. EAP Identity Response<br/>("user@acme.com")

    rect rgb(230, 240, 255)
    Note over NAS,RADIUS: 802.1X / EAP Exchange<br/>(encapsulated in RADIUS)
    NAS->>RADIUS: 4. Access-Request<br/>{User-Name, EAP-Message,<br/>NAS-IP, NAS-Port-Type}
    RADIUS->>NAS: 5. Access-Challenge<br/>{EAP challenge}
    NAS->>User: 6. EAP challenge forwarded
    User->>NAS: 7. EAP response (credentials)
    NAS->>RADIUS: 8. Access-Request<br/>{EAP response}
    end

    RADIUS->>LDAP: 9. Verify credentials<br/>(LDAP bind or Kerberos)
    LDAP->>RADIUS: 10. Authentication result +<br/>user group memberships

    RADIUS->>RADIUS: 11. Apply authorization policies<br/>based on user groups, device type,<br/>time of day, location

    alt Authentication Successful
        RADIUS->>NAS: 12. Access-Accept<br/>{VLAN assignment: 100,<br/>Session-Timeout: 28800,<br/>Filter-Id: "corp-policy"}
        NAS->>NAS: 13. Open port, assign VLAN,<br/>apply firewall policy
        NAS->>User: 14. Network access granted<br/>(on correct VLAN with policies)
    else Authentication Failed
        RADIUS->>NAS: 12. Access-Reject<br/>{Reply-Message: "Invalid credentials"}
        NAS->>User: 14. Network access denied
    end

RADIUS Authorization: Dynamic Network Policies

RADIUS does not just say "yes" or "no." The Access-Accept response includes attributes that dynamically configure the network for each user:

User GroupVLANPolicyReasoning
EngineersVLAN 100Full internal accessNeed access to dev/staging/prod systems
ContractorsVLAN 150Limited access, filteredOnly specific internal services
GuestsVLAN 200Internet-only, captive portalNo internal network visibility
IoT devicesVLAN 300Isolated, rate-limitedUntrusted devices, limited blast radius
QuarantineVLAN 999Remediation onlyFailed compliance checks

This means the same physical network infrastructure serves different access levels to different users, all controlled by RADIUS policies that can change in real-time based on group membership, device health, time of day, or risk signals.

# Test RADIUS authentication from the command line
radtest user 'password123' radius.acme.com 0 'shared_secret'
# Sending Access-Request of id 42 to radius.acme.com
# Access-Accept

# Debug RADIUS authentication (run on the RADIUS server)
radiusd -X  # Shows all authentication processing in real-time

# Send a detailed RADIUS test with attributes
radclient -x radius.acme.com auth 'shared_secret' <<EOF
User-Name = "user"
User-Password = "password123"
NAS-IP-Address = 10.0.1.1
NAS-Port = 1
NAS-Port-Type = Wireless-802.11
EOF

Federation and Cross-Domain Trust

What happens when Company A needs to authenticate users from Company B? Like a partner integration? That is federation -- establishing trust relationships across organizational boundaries. It is one of the most powerful and complex aspects of enterprise authentication.

graph TD
    subgraph "Company A (acme.com)"
        IdP_A["Acme IdP<br/>(Okta)"]
        Users_A["Acme Users"]
        Apps_A["Acme Internal Apps"]
    end

    subgraph "Company B (partner.com)"
        IdP_B["Partner IdP<br/>(Azure AD)"]
        Users_B["Partner Users"]
        Apps_B["Partner Apps"]
    end

    subgraph "SaaS Applications"
        Slack["Slack<br/>(SP)"]
        JIRA["JIRA<br/>(SP)"]
        SharedApp["Shared Project App<br/>(SP)"]
    end

    Users_A -->|"SAML/OIDC"| IdP_A
    Users_B -->|"SAML/OIDC"| IdP_B

    IdP_A -->|"SAML Assertion"| Slack
    IdP_A -->|"SAML Assertion"| JIRA
    IdP_A -->|"SAML Assertion"| SharedApp

    IdP_B -->|"SAML Assertion"| SharedApp

    IdP_A <-->|"Federation Trust<br/>(metadata exchange,<br/>certificate trust)"| IdP_B

    style SharedApp fill:#ffa94d,color:#000

Federation allows partner users to access shared applications without creating accounts in the other organization's identity system. The trust is established by exchanging SAML metadata (entity IDs, ACS URLs, signing certificates) between the IdPs. When a partner user accesses the shared application, the SP redirects them to their own IdP, which authenticates them and issues a SAML assertion that the SP trusts because of the pre-established federation relationship.

In Active Directory, federation manifests as forest trusts (between AD forests), external trusts (between specific domains), and realm trusts (between AD and non-Windows Kerberos realms). Each type has different transitivity properties and security implications.


How Enterprise Authentication Chains Together

In a typical enterprise, all these protocols work together. Here is how a user's typical morning involves every protocol discussed in this chapter, all in about ten minutes.

sequenceDiagram
    participant User as User's Laptop
    participant WiFi as Wi-Fi AP
    participant RADIUS as RADIUS Server
    participant DC as Domain Controller<br/>(KDC + LDAP + DNS)
    participant Exchange as Exchange Server
    participant Okta as Okta (IdP)
    participant Salesforce as Salesforce (SP)

    Note over User,RADIUS: 8:00 AM -- Connect to corporate Wi-Fi

    User->>WiFi: Associate with SSID "AcmeCorp"
    WiFi->>RADIUS: 802.1X/EAP: user@acme.com
    RADIUS->>DC: Verify credentials (LDAP/Kerberos)
    DC->>RADIUS: Valid + groups: [engineers]
    RADIUS->>WiFi: Accept, VLAN 100
    WiFi->>User: Connected on engineering VLAN

    Note over User,DC: 8:01 AM -- Windows domain login

    User->>DC: Kerberos AS-REQ<br/>(pre-auth with password)
    DC->>User: AS-REP: TGT<br/>(valid 10 hours)

    Note over User,Exchange: 8:05 AM -- Open Outlook

    User->>DC: TGS-REQ: need ticket<br/>for HTTP/exchange.acme.com
    DC->>User: TGS-REP: service ticket
    User->>Exchange: AP-REQ: service ticket
    Exchange->>User: AP-REP: Welcome!<br/>Email loads via Kerberos SSO

    Note over User,Salesforce: 8:30 AM -- Open Salesforce in browser

    User->>Salesforce: GET salesforce.com/dashboard
    Salesforce->>User: 302 Redirect to Okta<br/>(SAML AuthnRequest)
    User->>Okta: Follow redirect to Okta
    Okta->>Okta: User has active session<br/>(authenticated via IWA/Kerberos<br/>or earlier OIDC login)
    Okta->>User: SAML Response (signed assertion)
    User->>Salesforce: POST SAML Response to ACS
    Salesforce->>User: Dashboard loads!<br/>User never entered a password

The user typed their password once -- when logging into Windows -- and everything else just worked. That is the promise of single sign-on. One authentication event, propagated through Kerberos tickets and SAML assertions, grants access to Wi-Fi, email, file shares, and cloud applications. The user experience is seamless. The underlying complexity -- RADIUS for network access, Kerberos for domain authentication, SAML for web application SSO -- is enormous. But when it works, it is invisible. And that is the highest compliment you can pay an authentication system.

A company had a five-hop authentication chain for their SaaS applications:

1. User accesses a SaaS app (SP), which redirects to Azure AD
2. Azure AD is federated to on-premises ADFS (which acts as both SP and IdP)
3. ADFS authenticates the user via Kerberos against on-premises AD
4. ADFS issues a SAML assertion to Azure AD
5. Azure AD transforms the assertion and issues a new SAML assertion to the SaaS app

When it worked, the user saw two quick redirects and was logged in. When it broke -- and it broke regularly -- the debugging was a nightmare:

- A certificate on the ADFS server expired, breaking step 2
- Clock skew between ADFS and Azure AD exceeded the 5-minute tolerance, breaking step 4
- The SaaS app changed their ACS URL without notifying us, breaking step 5
- A Group Policy change broke Kerberos integrated Windows authentication, breaking step 3
- DNS changes during a network migration meant clients could not find the ADFS server

Each failure produced a different cryptic error -- sometimes an XML stack trace, sometimes a generic "login failed" page, sometimes an infinite redirect loop. Debugging required understanding every protocol in the chain and having access to logs on every system involved.

**The lesson:** SSO is either magic that just works, or a nightmare that fails in five different places simultaneously. There is no middle ground. Document every hop, monitor every certificate, and keep an architecture diagram that shows all the trust relationships. When it breaks at 2 AM, that diagram is the difference between a 20-minute fix and a 4-hour investigation.

What You've Learned

This chapter covered the major authentication protocols that power enterprise systems:

  • Kerberos uses a ticket-granting system where the password is used exactly once to obtain a TGT, and then tickets prove identity for all subsequent service access; the protocol's strength is that passwords never traverse the network after initial authentication; key attacks include Kerberoasting (cracking service account passwords offline) and Golden Ticket (forging TGTs with compromised krbtgt hash)
  • LDAP is a hierarchical directory protocol that stores identity data and supports authentication through bind operations; it must always be protected with TLS (LDAPS or StartTLS) because simple binds transmit passwords in cleartext; LDAP injection is a real vulnerability that requires proper input escaping
  • Active Directory combines Kerberos, LDAP, DNS, and Group Policy into Microsoft's unified enterprise identity platform; NTLM remains a legacy risk that should be disabled where possible
  • SAML is an XML-based SSO protocol with three roles (User, Identity Provider, Service Provider); it is complex but ubiquitous in enterprise environments; XML Signature Wrapping attacks and assertion replay are the primary security concerns
  • RADIUS controls network access through 802.1X, authenticating users before they can even reach the network and dynamically assigning VLAN and firewall policies based on user identity and group membership
  • Federation enables cross-organization authentication through trust relationships, allowing partner users to access shared applications without duplicate accounts
  • Enterprise authentication chains combine all these protocols: RADIUS for network access, Kerberos for domain authentication, SAML for web application SSO -- all triggered by a single password entry

Those four redirects during login? That was SAML doing its dance between the SP and IdP. Redirect to the SP, redirect to the IdP, authenticate at the IdP, POST assertion back to the SP. Four hops, each carrying cryptographic proof of your identity across organizational boundaries. Understanding what happens behind those redirects is the difference between configuring auth by following a tutorial and being able to debug auth when it breaks at 2 AM on a Saturday.