Back to Technology

Complete Protocols Master Part 15: Authentication Protocols

January 31, 2026 Wasil Zafar 42 min read

Master identity protocols: OAuth 2.0 for authorization, OpenID Connect for identity, SAML for enterprise, and Kerberos for domain authentication.

Table of Contents

  1. Introduction
  2. OAuth 2.0
  3. OpenID Connect
  4. SAML 2.0
  5. Kerberos
  6. LDAP
  7. Comparison
  8. Summary

Introduction: Authentication vs Authorization

Authentication and authorization are distinct but related concepts. Authentication verifies WHO you are. Authorization determines WHAT you can do.

Series Context: This is Part 15 of 20 in the Complete Protocols Master series. Authentication protocols operate at the Application Layer (Layer 7).
Concepts

Authentication vs Authorization

Authentication vs Authorization:

AUTHENTICATION (AuthN)
"Who are you?"
• Verifies identity
• Username/password
• Certificates
• Biometrics

AUTHORIZATION (AuthZ)
"What can you access?"
• Permissions
• Roles
• Access control
• Scopes

Example - Hotel:
• AuthN: Show ID at check-in (prove identity)
• AuthZ: Key card grants room access (not other rooms)

Protocol Mapping:
• OAuth 2.0: Authorization only
• OpenID Connect: Authentication (built on OAuth)
• SAML: Both (primarily AuthN)
• Kerberos: Both (tickets grant access)
• LDAP: Directory (stores identity data)
Overview

Protocol Landscape

ProtocolTypeBest ForToken Format
OAuth 2.0AuthorizationAPI access, mobileOpaque/JWT
OIDCAuthN + AuthZWeb login, SSOJWT (ID Token)
SAML 2.0AuthN + AuthZEnterprise SSOXML Assertion
KerberosAuthN + AuthZActive DirectoryTickets
LDAPDirectoryUser lookupN/A

OAuth 2.0: Authorization Framework

OAuth 2.0 is THE authorization standard for APIs. It lets users grant apps limited access without sharing credentials. "Log in with Google" uses OAuth.

Sequence diagram of the OAuth 2.0 Authorization Code flow showing interactions between resource owner, client app, authorization server, and resource server
OAuth 2.0 Authorization Code flow — the user authorizes the client app at the authorization server, which issues tokens for API access without exposing credentials
OAuth 2.0 Authorization Code Flow
sequenceDiagram
    participant U as User Browser
    participant A as Application
    participant AS as Authorization Server
    participant RS as Resource Server

    U->>A: Click Login
    A->>AS: Redirect with client_id, scope, redirect_uri
    U->>AS: Authenticate and Grant Permission
    AS->>A: Authorization Code via redirect

    A->>AS: Exchange Code + client_secret
    Note right of A: Server-to-server request
    AS->>A: Access Token + Refresh Token

    A->>RS: API Request + Access Token
    RS->>A: Protected Resource Data
                        
Key Insight: OAuth 2.0 is about AUTHORIZATION, not authentication. It answers "Can this app access my photos?" not "Who is this user?"
Roles

OAuth 2.0 Actors

OAuth 2.0 Roles:

1. RESOURCE OWNER
   The user who owns the data
   Example: You

2. CLIENT
   The application wanting access
   Example: Third-party photo app

3. AUTHORIZATION SERVER
   Issues tokens after user consent
   Example: Google's auth server

4. RESOURCE SERVER
   Hosts the protected data
   Example: Google Photos API

Flow:
User → "I want to use PhotoApp"
PhotoApp → "Please authorize at Google"
User → Google: "Yes, allow read-only photos"
Google → PhotoApp: "Here's an access token"
PhotoApp → Google Photos API: "Token: xyz, get photos"
API → PhotoApp: "Here are the photos"
Grant Types

OAuth 2.0 Flows

OAuth 2.0 Grant Types:

1. AUTHORIZATION CODE (most secure)
   Best for: Server-side web apps
   Flow: Code exchanged for token server-side
   
2. AUTHORIZATION CODE + PKCE
   Best for: Mobile apps, SPAs
   Flow: Code + code_verifier for security
   PKCE = Proof Key for Code Exchange

3. CLIENT CREDENTIALS
   Best for: Machine-to-machine (no user)
   Flow: App authenticates directly

4. DEVICE CODE
   Best for: TVs, CLI tools (no browser)
   Flow: User authorizes on separate device

5. IMPLICIT (deprecated)
   Was for: SPAs (replaced by PKCE)
   Issue: Token in URL fragment
# Authorization Code Flow with PKCE

# Step 1: Generate code verifier and challenge
code_verifier="random-43-to-128-character-string"
code_challenge=$(echo -n "$code_verifier" | sha256sum | cut -d' ' -f1 | xxd -r -p | base64 -w0 | tr '+/' '-_' | tr -d '=')

# Step 2: Redirect user to authorization
https://auth.example.com/authorize?
  client_id=my-app&
  response_type=code&
  redirect_uri=https://myapp.com/callback&
  scope=read:photos&
  state=random-csrf-token&
  code_challenge=$code_challenge&
  code_challenge_method=S256

# Step 3: User logs in and consents
# Redirected back with authorization code

# Step 4: Exchange code for tokens (server-side)
curl -X POST https://auth.example.com/token \
  -d "grant_type=authorization_code" \
  -d "code=AUTHORIZATION_CODE" \
  -d "redirect_uri=https://myapp.com/callback" \
  -d "client_id=my-app" \
  -d "code_verifier=$code_verifier"

# Response:
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g..."
}
# OAuth 2.0 Client Credentials Flow (Python)

import requests
import base64

def client_credentials_flow():
    """Machine-to-machine authentication"""
    
    client_id = "my-service"
    client_secret = "super-secret"
    token_url = "https://auth.example.com/token"
    
    # Request access token
    response = requests.post(
        token_url,
        data={
            "grant_type": "client_credentials",
            "scope": "api:read api:write"
        },
        auth=(client_id, client_secret)
    )
    
    tokens = response.json()
    access_token = tokens["access_token"]
    
    print(f"Access Token: {access_token[:50]}...")
    print(f"Expires in: {tokens['expires_in']} seconds")
    
    # Use token to call API
    api_response = requests.get(
        "https://api.example.com/data",
        headers={"Authorization": f"Bearer {access_token}"}
    )
    
    return api_response.json()

# Token structure (JWT)
print("""
JWT Structure:
HEADER.PAYLOAD.SIGNATURE

Header: {"alg": "RS256", "typ": "JWT"}
Payload: {
  "iss": "https://auth.example.com",
  "sub": "user123",
  "aud": "api.example.com",
  "exp": 1735689600,
  "scope": "read:photos"
}
Signature: RSASHA256(header + payload, private_key)
""")

OpenID Connect: Identity Layer

OpenID Connect (OIDC) adds authentication to OAuth 2.0. It provides an ID Token that proves who the user is—not just what they can access.

Diagram showing how OpenID Connect layers identity on top of OAuth 2.0 by adding an ID Token JWT with user claims alongside the access token
OpenID Connect adds an identity layer to OAuth 2.0 — the ID Token (JWT) contains user claims like name and email, while the access token handles API authorization
OIDC

OIDC vs OAuth 2.0

OIDC = OAuth 2.0 + Identity

What OIDC Adds:
• ID Token (JWT proving identity)
• UserInfo endpoint
• Standard claims (name, email, picture)
• Discovery endpoint

Tokens in OIDC:
• Access Token: API authorization (same as OAuth)
• ID Token: User identity (NEW in OIDC)
• Refresh Token: Get new tokens (same as OAuth)

ID Token Claims:
{
  "iss": "https://accounts.google.com",
  "sub": "110169484474386276334",  // Unique user ID
  "aud": "my-client-id",
  "exp": 1735689600,
  "iat": 1735686000,
  "name": "Jane Doe",
  "email": "jane@example.com",
  "picture": "https://..."
}
# OIDC Discovery Endpoint

curl https://accounts.google.com/.well-known/openid-configuration

# Returns:
{
  "issuer": "https://accounts.google.com",
  "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
  "token_endpoint": "https://oauth2.googleapis.com/token",
  "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
  "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
  "scopes_supported": ["openid", "email", "profile"],
  "response_types_supported": ["code", "token", "id_token"],
  "claims_supported": ["sub", "name", "email", "picture"]
}
# OIDC Authentication (Python with Authlib)

def oidc_example():
    """Demonstrate OIDC authentication"""
    
    print("OIDC Authentication Flow")
    print("=" * 50)
    
    print("""
    # Flask OIDC Example
    
    from authlib.integrations.flask_client import OAuth
    
    oauth = OAuth(app)
    oauth.register(
        name='google',
        client_id='your-client-id',
        client_secret='your-client-secret',
        server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
        client_kwargs={'scope': 'openid email profile'}
    )
    
    @app.route('/login')
    def login():
        redirect_uri = url_for('callback', _external=True)
        return oauth.google.authorize_redirect(redirect_uri)
    
    @app.route('/callback')
    def callback():
        token = oauth.google.authorize_access_token()
        
        # ID Token contains user identity
        id_token = token['id_token']
        userinfo = token['userinfo']
        
        # Now you know WHO the user is
        user_id = userinfo['sub']
        email = userinfo['email']
        name = userinfo['name']
        
        return f'Hello {name}!'
    """)
    
    print("\nKey OIDC Scopes:")
    print("• openid: Required, returns ID token")
    print("• profile: Name, picture, etc.")
    print("• email: Email address")
    print("• address: Physical address")
    print("• phone: Phone number")

oidc_example()

SAML 2.0: Enterprise SSO

SAML (Security Assertion Markup Language) is the enterprise standard for Single Sign-On. XML-based, mature, used by most corporate identity providers.

Sequence diagram of the SAML 2.0 SP-initiated SSO flow showing user, service provider, and identity provider interactions with SAML request and assertion exchange
SAML 2.0 SP-initiated SSO flow — the user accesses the service provider, gets redirected to the identity provider for authentication, and returns with a signed SAML assertion
SAML

SAML Components

SAML 2.0 Components:

1. IDENTITY PROVIDER (IdP)
   • Authenticates users
   • Issues SAML assertions
   • Examples: Okta, Azure AD, ADFS

2. SERVICE PROVIDER (SP)
   • Your application
   • Trusts the IdP
   • Consumes assertions

3. SAML ASSERTION
   • XML document proving identity
   • Contains attributes (email, groups)
   • Digitally signed by IdP

SAML Flows:
• SP-Initiated: User starts at app, redirected to IdP
• IdP-Initiated: User starts at IdP portal

SP-Initiated Flow:
User → SP: "Access app"
SP → User: "Redirect to IdP"
User → IdP: "Login page"
User → IdP: "Credentials"
IdP → User: "SAML Response (redirect to SP)"
User → SP: "Here's SAML Response"
SP → User: "Authenticated! Welcome"
# SAML Response Structure (simplified)

<samlp:Response>
  <Issuer>https://idp.example.com</Issuer>
  <Status>
    <StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
  </Status>
  
  <Assertion>
    <Issuer>https://idp.example.com</Issuer>
    
    <Subject>
      <NameID>user@example.com</NameID>
    </Subject>
    
    <Conditions NotBefore="2026-01-31T00:00:00Z" 
                NotOnOrAfter="2026-01-31T00:05:00Z">
      <AudienceRestriction>
        <Audience>https://sp.example.com</Audience>
      </AudienceRestriction>
    </Conditions>
    
    <AttributeStatement>
      <Attribute Name="email">
        <AttributeValue>user@example.com</AttributeValue>
      </Attribute>
      <Attribute Name="groups">
        <AttributeValue>admin</AttributeValue>
        <AttributeValue>developers</AttributeValue>
      </Attribute>
    </AttributeStatement>
    
    <Signature>...</Signature>
  </Assertion>
</samlp:Response>
# SAML Service Provider (Python with python-saml)

def saml_sp_example():
    """Demonstrate SAML Service Provider"""
    
    print("SAML Service Provider Configuration")
    print("=" * 50)
    
    print("""
    # Flask SAML Example (using python3-saml)
    
    from onelogin.saml2.auth import OneLogin_Saml2_Auth
    
    @app.route('/saml/login')
    def saml_login():
        req = prepare_flask_request(request)
        auth = OneLogin_Saml2_Auth(req, custom_base_path=SAML_PATH)
        return redirect(auth.login())
    
    @app.route('/saml/acs', methods=['POST'])
    def saml_acs():
        # Assertion Consumer Service - receives SAML Response
        req = prepare_flask_request(request)
        auth = OneLogin_Saml2_Auth(req, custom_base_path=SAML_PATH)
        
        auth.process_response()
        errors = auth.get_errors()
        
        if not errors:
            # Authentication successful
            attributes = auth.get_attributes()
            name_id = auth.get_nameid()
            
            user_email = attributes.get('email', [None])[0]
            user_groups = attributes.get('groups', [])
            
            # Create session
            session['user'] = {
                'email': user_email,
                'groups': user_groups
            }
            
            return redirect('/dashboard')
        else:
            return f'SAML Error: {errors}', 400
    """)
    
    print("\nSAML Metadata Exchange:")
    print("• SP publishes metadata (entity ID, ACS URL, cert)")
    print("• IdP publishes metadata (SSO URL, signing cert)")
    print("• Both import each other's metadata")

saml_sp_example()

Kerberos: Network Authentication

Kerberos is the authentication backbone of Active Directory. Uses tickets instead of passwords—users authenticate once, get tickets for services.

Architecture diagram of Kerberos authentication showing the KDC with Authentication Server and Ticket Granting Server, TGT and service ticket exchanges
Kerberos ticket-based authentication — the user obtains a TGT from the Authentication Server, then exchanges it at the TGS for service tickets without re-entering credentials
Kerberos

Kerberos Architecture

Kerberos Components:

1. KDC (Key Distribution Center)
   • Authentication Server (AS)
   • Ticket Granting Server (TGS)
   • Holds all secrets

2. TICKETS
   • TGT (Ticket Granting Ticket): Proves identity
   • Service Ticket: Grants access to specific service

3. PRINCIPALS
   • Users: user@REALM.COM
   • Services: HTTP/web.realm.com@REALM.COM

Kerberos Authentication:
User → AS: "I'm alice, want TGT"
AS → User: "Here's TGT (encrypted)"
User → TGS: "TGT, want ticket for fileserver"
TGS → User: "Here's service ticket"
User → FileServer: "Service ticket"
FileServer → User: "Access granted"

Key Concept: User password NEVER sent over network
# Kerberos ticket management (Linux)

# Initialize (get TGT)
kinit alice@EXAMPLE.COM
# Enter password (locally hashed, not sent)

# List tickets
klist
# Ticket cache: FILE:/tmp/krb5cc_1000
# Default principal: alice@EXAMPLE.COM
# 
# Valid starting       Expires              Service principal
# 01/31/2026 09:00    01/31/2026 19:00     krbtgt/EXAMPLE.COM@EXAMPLE.COM

# Get service ticket (automatic when accessing service)
curl --negotiate -u : http://web.example.com/

# Destroy tickets (logout)
kdestroy

# Check keytab (for service accounts)
klist -k /etc/krb5.keytab
# Kerberos authentication in Python

def kerberos_example():
    """Kerberos authentication demonstration"""
    
    print("Kerberos in Python")
    print("=" * 50)
    
    print("""
    # Using requests-kerberos
    
    import requests
    from requests_kerberos import HTTPKerberosAuth
    
    # Auto-use cached TGT
    response = requests.get(
        'https://api.example.com/data',
        auth=HTTPKerberosAuth()
    )
    
    # For SPNEGO (HTTP Negotiate)
    from requests_kerberos import HTTPKerberosAuth, OPTIONAL
    
    response = requests.get(
        'https://api.example.com/data',
        auth=HTTPKerberosAuth(mutual_authentication=OPTIONAL)
    )
    """)
    
    print("\nKerberos vs Password Auth:")
    print("• Kerberos: Ticket-based, password never leaves client")
    print("• Password: Sent to server (even if encrypted)")
    print("• Kerberos: Mutual auth (server proves identity too)")
    print("• Kerberos: Time-limited tickets, auto-expire")

kerberos_example()

LDAP: Directory Services

LDAP (Lightweight Directory Access Protocol) stores and retrieves identity data. It's not strictly an authentication protocol—it's the database that backs authentication.

Tree diagram of an LDAP Directory Information Tree showing the hierarchical structure with domain components, organizational units, and user entries
LDAP Directory Information Tree (DIT) — entries are organized hierarchically under domain components, with organizational units grouping users, groups, and other objects
LDAP

LDAP Structure

LDAP Concepts:

1. DIRECTORY TREE (DIT)
   Hierarchical structure
   dc=example,dc=com (domain)
    └── ou=People
         └── uid=alice
         └── uid=bob
    └── ou=Groups
         └── cn=developers

2. DISTINGUISHED NAME (DN)
   Unique identifier for entry
   uid=alice,ou=People,dc=example,dc=com

3. ATTRIBUTES
   cn: Common Name
   uid: User ID
   mail: Email
   userPassword: Hashed password
   memberOf: Group membership

4. OPERATIONS
   • Bind: Authenticate
   • Search: Query
   • Add/Modify/Delete: CRUD
# LDAP queries with ldapsearch

# Anonymous search (if allowed)
ldapsearch -x -H ldap://ldap.example.com \
  -b "dc=example,dc=com" "(uid=alice)"

# Authenticated search
ldapsearch -x -H ldap://ldap.example.com \
  -D "cn=admin,dc=example,dc=com" -W \
  -b "ou=People,dc=example,dc=com" "(objectClass=person)"

# Search specific attributes
ldapsearch -x -H ldap://ldap.example.com \
  -b "dc=example,dc=com" "(uid=alice)" cn mail memberOf

# Test authentication (bind)
ldapwhoami -x -H ldap://ldap.example.com \
  -D "uid=alice,ou=People,dc=example,dc=com" -W
# LDAP authentication in Python

import ldap3

def ldap_auth(username, password):
    """Authenticate user against LDAP"""
    
    server = ldap3.Server('ldap://ldap.example.com', get_info=ldap3.ALL)
    user_dn = f"uid={username},ou=People,dc=example,dc=com"
    
    try:
        conn = ldap3.Connection(server, user_dn, password, auto_bind=True)
        print(f"✅ Authentication successful for {username}")
        
        # Search for user attributes
        conn.search(
            user_dn,
            '(objectClass=person)',
            attributes=['cn', 'mail', 'memberOf']
        )
        
        if conn.entries:
            user = conn.entries[0]
            print(f"   Name: {user.cn}")
            print(f"   Email: {user.mail}")
            print(f"   Groups: {user.memberOf}")
        
        conn.unbind()
        return True
        
    except ldap3.core.exceptions.LDAPBindError:
        print(f"❌ Authentication failed for {username}")
        return False

# Example
# ldap_auth('alice', 'password123')

Protocol Comparison

Decision Guide

Choosing Authentication Protocol

ScenarioBest ChoiceReason
Modern web appOIDCIndustry standard, JWT tokens
Mobile appOAuth 2.0 + PKCESecure, no client secret
Enterprise SSOSAMLMature, wide support
Windows domainKerberosBuilt into AD
API authorizationOAuth 2.0Scoped access tokens
Machine-to-machineOAuth 2.0 CCNo user involved
OIDC vs SAML

When to Use Which

OIDC vs SAML:

OIDC (Modern):
✅ JSON/JWT (lightweight)
✅ Mobile-friendly
✅ REST APIs
✅ Modern IdPs (Auth0, Okta)
✅ Easier to implement

SAML (Enterprise):
✅ Mature, battle-tested
✅ Wide enterprise support
✅ Complex attribute mapping
✅ Legacy system integration
❌ XML (verbose)
❌ Complex to implement

Choose OIDC for:
• New applications
• Mobile apps
• API-first architecture
• Consumer applications

Choose SAML for:
• Enterprise with existing SAML IdP
• Legacy system integration
• Complex attribute requirements
• Regulatory compliance

Summary & Next Steps

Key Takeaways:
  • OAuth 2.0: Authorization framework for API access
  • OIDC: Identity layer on OAuth, provides ID tokens
  • SAML: Enterprise SSO with XML assertions
  • Kerberos: Ticket-based auth for AD domains
  • LDAP: Directory for storing identity data
Quiz

Test Your Knowledge

  1. OAuth 2.0 vs OIDC? (AuthZ vs AuthN)
  2. What is PKCE? (Proof Key for Code Exchange, mobile security)
  3. SAML vs OIDC token format? (XML vs JWT)
  4. What's a TGT in Kerberos? (Ticket Granting Ticket)
  5. LDAP purpose? (Directory storage, identity lookup)