Kerberos Authentication in Python — Deep Dive

Kerberos ticket internals

A Kerberos ticket contains the client’s principal name, the service’s principal name, a session key, validity timestamps, and optional authorization data. The entire ticket is encrypted with the service’s long-term key — only the service can decrypt it.

The TGT is just a ticket for the special service krbtgt/REALM@REALM. When you present it to the TGS, the TGS decrypts it with the krbtgt key, verifies you’re legitimate, and issues a new ticket for the requested service.

Tickets have a limited lifetime (typically 10 hours) and are renewable for a longer period (typically 7 days). After expiration, the client must re-authenticate.

Setting up the environment

On Linux, install MIT Kerberos:

# Debian/Ubuntu
sudo apt install krb5-user libkrb5-dev

# RHEL/CentOS
sudo yum install krb5-workstation krb5-devel

Configure /etc/krb5.conf:

[libdefaults]
    default_realm = EXAMPLE.COM
    dns_lookup_realm = false
    dns_lookup_kdc = true
    ticket_lifetime = 10h
    renew_lifetime = 7d
    forwardable = true

[realms]
    EXAMPLE.COM = {
        kdc = dc1.example.com
        kdc = dc2.example.com
        admin_server = dc1.example.com
    }

[domain_realm]
    .example.com = EXAMPLE.COM
    example.com = EXAMPLE.COM

Obtaining tickets with kinit

For interactive use, kinit prompts for your password and caches a TGT:

kinit user@EXAMPLE.COM
klist  # shows cached tickets

For services (daemons, cron jobs), use a keytab file — a file containing the service’s long-term key, allowing authentication without a password:

# Create a keytab for a service principal
ktutil
> addent -password -p HTTP/webapp.example.com@EXAMPLE.COM -k 1 -e aes256-cts
> wkt /etc/krb5.keytab
> quit

# Authenticate using keytab
kinit -kt /etc/krb5.keytab HTTP/webapp.example.com@EXAMPLE.COM

HTTP Kerberos with requests-kerberos

The requests-kerberos library implements SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) — the protocol that carries Kerberos tokens over HTTP.

import requests
from requests_kerberos import HTTPKerberosAuth, REQUIRED

# Basic usage — uses cached TGT from kinit
kerberos_auth = HTTPKerberosAuth(mutual_authentication=REQUIRED)
response = requests.get(
    "https://intranet.example.com/api/users",
    auth=kerberos_auth,
)

# The library handles the Negotiate handshake:
# 1. Initial request gets 401 with WWW-Authenticate: Negotiate
# 2. Library obtains service ticket for HTTP/intranet.example.com
# 3. Retries request with Authorization: Negotiate <base64 token>
# 4. Server responds with Negotiate token (mutual auth proof)

For mutual authentication, the server proves its identity back to the client. Setting mutual_authentication=REQUIRED ensures the server isn’t impersonating the real service.

Low-level GSSAPI usage

For non-HTTP protocols or custom authentication flows, use gssapi directly:

import gssapi

# Client side: initiate security context
service_name = gssapi.Name(
    "HTTP@webapp.example.com",
    name_type=gssapi.NameType.hostbased_service,
)

ctx = gssapi.SecurityContext(
    name=service_name,
    usage="initiate",
    flags=[
        gssapi.RequirementFlag.mutual_authentication,
        gssapi.RequirementFlag.integrity,
    ],
)

# Generate the initial token
initial_token = ctx.step(b"")

# Send initial_token to the server...
# Receive server_token back...

# Complete the handshake
ctx.step(server_token)

if ctx.complete:
    print("Authentication successful")
    # Now you can use ctx.wrap() and ctx.unwrap() for message protection

Keytab-based authentication for services

Production services authenticate using keytabs instead of passwords:

import gssapi
import os

# Point to the keytab file
os.environ["KRB5_KTNAME"] = "/etc/webapp/service.keytab"

# Server side: accept incoming security contexts
server_creds = gssapi.Credentials(
    name=gssapi.Name("HTTP@webapp.example.com",
                     name_type=gssapi.NameType.hostbased_service),
    usage="accept",
)

def authenticate_client(client_token):
    """Validate an incoming Kerberos token."""
    ctx = gssapi.SecurityContext(creds=server_creds, usage="accept")
    output_token = ctx.step(client_token)

    if ctx.complete:
        client_name = str(ctx.initiator_name)  # e.g., "user@EXAMPLE.COM"
        return client_name, output_token

    return None, output_token

Flask integration with SPNEGO

from flask import Flask, request, Response, g
import gssapi
import base64
import os

app = Flask(__name__)
os.environ["KRB5_KTNAME"] = "/etc/webapp/http.keytab"

@app.before_request
def kerberos_authenticate():
    auth_header = request.headers.get("Authorization", "")

    if not auth_header.startswith("Negotiate "):
        return Response(
            "Unauthorized",
            status=401,
            headers={"WWW-Authenticate": "Negotiate"},
        )

    client_token = base64.b64decode(auth_header[10:])

    try:
        server_creds = gssapi.Credentials(
            name=gssapi.Name("HTTP@" + request.host.split(":")[0],
                            name_type=gssapi.NameType.hostbased_service),
            usage="accept",
        )
        ctx = gssapi.SecurityContext(creds=server_creds, usage="accept")
        output_token = ctx.step(client_token)

        if ctx.complete:
            g.user = str(ctx.initiator_name)
            g.kerberos_token = base64.b64encode(output_token).decode()
        else:
            return Response("Unauthorized", status=401)

    except gssapi.exceptions.GSSError as e:
        return Response(f"Authentication failed: {e}", status=401)

@app.after_request
def add_negotiate_header(response):
    if hasattr(g, "kerberos_token"):
        response.headers["WWW-Authenticate"] = f"Negotiate {g.kerberos_token}"
    return response

Connecting to Kerberos-protected databases

SQL Server and PostgreSQL support Kerberos authentication:

# SQL Server with pyodbc
import pyodbc

conn = pyodbc.connect(
    "DRIVER={ODBC Driver 18 for SQL Server};"
    "SERVER=sqlserver.example.com;"
    "DATABASE=mydb;"
    "Trusted_Connection=yes;"  # uses Kerberos ticket
)

# PostgreSQL with psycopg2 (GSSAPI)
import psycopg2

conn = psycopg2.connect(
    host="pgserver.example.com",
    dbname="mydb",
    # No user/password — uses Kerberos
)

Both require the client to have a valid TGT and the correct SPN registered for the database service.

Delegation and constrained delegation

Sometimes a service needs to act on behalf of the user when talking to a backend service. Delegation forwards the user’s TGT to the intermediate service, allowing it to obtain service tickets as the user.

# Enable delegation in requests-kerberos
from requests_kerberos import HTTPKerberosAuth, REQUIRED

auth = HTTPKerberosAuth(
    mutual_authentication=REQUIRED,
    delegate=True,  # forward TGT to the service
)

Constrained delegation (AD-specific) limits which services the intermediate service can contact on behalf of the user. This is configured in Active Directory, not in your Python code, but understanding it matters when your web app needs to proxy requests to a backend database.

Troubleshooting common failures

“Server not found in Kerberos database” — the SPN doesn’t exist in the KDC. Verify with kvno HTTP/webapp.example.com on Linux or setspn -Q HTTP/webapp.example.com on Windows.

Clock skew too great — Kerberos requires clocks synchronized within 5 minutes. Use NTP on all machines.

“No credentials cache found” — no TGT available. Run kinit or ensure the keytab path is correct.

Wrong enctype — older systems may not support AES256. Check klist -e for encryption types and ensure /etc/krb5.conf permits the types the KDC expects.

# Diagnostic commands
klist -e          # show tickets with encryption types
kvno HTTP/host    # request service ticket and show version
KRB5_TRACE=/dev/stderr kinit user@REALM  # verbose Kerberos trace

Security considerations

  • Protect keytab files — they’re equivalent to passwords. Set permissions to 600 and owned by the service user
  • Rotate keytab keys periodically — use ktutil or AD tools to generate new keys
  • Use AES encryption — disable RC4 (arcfour-hmac) when possible; it’s vulnerable to offline cracking
  • Enable Kerberos audit logging on the KDC to detect ticket-granting anomalies
  • Don’t enable delegation unless necessary — unconstrained delegation makes a compromised service extremely dangerous

The one thing to remember: Kerberos in Python means managing keytabs for service authentication, handling SPNEGO negotiation for HTTP, and using GSSAPI for custom protocols — with clock sync, SPN configuration, and keytab protection as the most common operational challenges.

pythonsecurityauthenticationenterprise

See Also