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
600and owned by the service user - Rotate keytab keys periodically — use
ktutilor 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.
See Also
- Python Api Key Management Why apps use special passwords called API keys, and how to keep them safe — explained with a library card analogy
- Python Attribute Based Access Control How apps make fine-grained permission decisions based on who you are, what you're accessing, and the circumstances — explained with an airport analogy
- Python Audit Logging Learn Audit Logging with a clear mental model so your Python code is easier to trust and maintain.
- Python Bandit Security Scanning Why Bandit Security Scanning helps Python teams catch painful mistakes early without slowing daily development.
- Python Clickjacking Prevention How invisible website layers trick you into clicking the wrong thing, and how Python apps stop it