User Authentication¶
Gemini uses client certificates for authentication instead of passwords or OAuth. In this tutorial, you'll learn how to implement certificate-based authentication in your capsule.
What You'll Learn¶
- How Gemini certificate authentication works
- Using
@require_certificatefor protected routes - Accessing certificate information
- Whitelisting specific certificates with
@require_fingerprint - Optional authentication with
@optional_certificate
How Certificate Auth Works¶
In HTTP, authentication typically uses:
- Username/password
- OAuth tokens
- Session cookies
Gemini uses TLS client certificates:
- The user generates a certificate (usually their Gemini client does this)
- When visiting a protected page, the server requests a certificate
- The client presents the certificate
- The server identifies the user by their certificate fingerprint
This provides:
- Persistent identity without passwords
- Cryptographic proof of identity
- User-controlled credentials
Step 1: Basic Protected Route¶
Create auth_app.py:
from xitzin import Xitzin, Request
from xitzin.auth import require_certificate, get_identity
app = Xitzin(title="Auth Demo")
@app.gemini("/")
def home(request: Request):
return """# Auth Demo
=> /public Public Page
=> /private Private Page (requires certificate)
=> /profile Your Profile
"""
@app.gemini("/public")
def public(request: Request):
return """# Public Page
Anyone can see this!
=> / Home
"""
@app.gemini("/private")
@require_certificate
def private(request: Request):
identity = get_identity(request)
return f"""# Private Page
Welcome! Your certificate ID: {identity.short_id}
This page is only visible to authenticated users.
=> / Home
"""
if __name__ == "__main__":
app.run()
The @require_certificate decorator:
- Returns status 60 (Certificate Required) if no certificate is provided
- Allows access if a valid certificate is presented
- Works with any valid client certificate
Step 2: Understanding Certificate Identity¶
The get_identity() function returns a CertificateIdentity object:
from xitzin.auth import get_identity, CertificateIdentity
@app.gemini("/whoami")
@require_certificate
def whoami(request: Request):
identity: CertificateIdentity = get_identity(request)
return f"""# Your Identity
Full fingerprint: {identity.fingerprint}
Short ID: {identity.short_id}
The fingerprint is a SHA-256 hash of your certificate.
=> / Home
"""
Properties of CertificateIdentity:
fingerprint: Full SHA-256 hash (64 characters)short_id: First 16 characters (for display)cert: The raw certificate object (if available)
Step 3: Admin Access with Fingerprint Whitelist¶
For admin panels, you might want to restrict access to specific certificates:
from xitzin.auth import require_fingerprint
# List of allowed admin certificate fingerprints
ADMIN_FINGERPRINTS = [
"a1b2c3d4e5f6789...", # Alice's certificate
"9876543210fedcba...", # Bob's certificate
]
@app.gemini("/admin")
@require_fingerprint(*ADMIN_FINGERPRINTS)
def admin(request: Request):
return """# Admin Panel
Welcome, administrator!
=> /admin/users Manage Users
=> /admin/content Manage Content
=> / Home
"""
The @require_fingerprint() decorator:
- Returns status 60 if no certificate is provided
- Returns status 61 (Certificate Not Authorized) if the fingerprint isn't in the list
- Only allows access to whitelisted certificates
Step 4: Optional Authentication¶
Sometimes you want to show different content based on whether the user is authenticated:
from xitzin.auth import optional_certificate
@app.gemini("/profile")
@optional_certificate
def profile(request: Request):
identity = request.state.identity
if identity:
return f"""# Your Profile
Certificate ID: {identity.short_id}
You're authenticated! Here's your personalized content.
=> /settings Your Settings
=> / Home
"""
else:
return """# Profile
You're browsing anonymously.
To see your profile, please enable client certificates in your Gemini client.
=> / Home
"""
The @optional_certificate decorator:
- Sets
request.state.identityto aCertificateIdentityorNone - Never blocks access
- Lets you customize content based on authentication status
Step 5: Building a User System¶
Here's a more complete example with user registration:
from xitzin import Xitzin, Request
from xitzin.auth import (
require_certificate,
optional_certificate,
get_identity,
)
app = Xitzin(title="User System")
# Simple in-memory user store
users = {}
@app.gemini("/")
@optional_certificate
def home(request: Request):
identity = request.state.identity
if identity and identity.fingerprint in users:
user = users[identity.fingerprint]
return f"""# Welcome back, {user['name']}!
=> /dashboard Your Dashboard
=> /logout Sign Out
"""
elif identity:
return """# Welcome!
You have a certificate but haven't registered yet.
=> /register Register
"""
else:
return """# Welcome!
=> /about About This Capsule
"""
@app.gemini("/register")
@require_certificate
def register_start(request: Request):
identity = get_identity(request)
if identity.fingerprint in users:
return """# Already Registered
You already have an account!
=> /dashboard Go to Dashboard
"""
return f"""# Register
Your certificate ID: {identity.short_id}
=> /register/name Choose Your Name
"""
@app.input("/register/name", prompt="Enter your display name:")
@require_certificate
def register_name(request: Request, query: str):
identity = get_identity(request)
users[identity.fingerprint] = {
"name": query,
"created": "today",
}
return f"""# Registration Complete!
Welcome, {query}!
Your account is now linked to your certificate.
=> /dashboard Go to Dashboard
=> / Home
"""
@app.gemini("/dashboard")
@require_certificate
def dashboard(request: Request):
identity = get_identity(request)
user = users.get(identity.fingerprint)
if not user:
return "# Not Registered\n\n=> /register Register First"
return f"""# Dashboard
Hello, {user['name']}!
Account created: {user['created']}
=> /dashboard/settings Settings
=> / Home
"""
@app.gemini("/logout")
def logout(request: Request):
return """# Signed Out
To fully sign out, you may need to close your Gemini client or disable your certificate.
=> / Home
"""
if __name__ == "__main__":
app.run()
Testing with Certificates¶
When testing, you can simulate certificates:
from xitzin.testing import TestClient
client = TestClient(app)
# Without certificate
response = client.get("/private")
assert response.status == 60 # Certificate Required
# With certificate
auth_client = client.with_certificate("test-fingerprint-123")
response = auth_client.get("/private")
assert response.is_success
Key Takeaways¶
@require_certificateenforces authentication@require_fingerprint()restricts to specific certificates@optional_certificateenables optional personalizationget_identity()retrieves the certificate identity- Fingerprints are the primary way to identify users
Security Considerations¶
- Fingerprints are public identifiers (like usernames)
- The private key never leaves the user's device
- Consider what happens if a user loses their certificate
- Provide a way to add multiple certificates to an account
Next Steps¶
- Build a complete guestbook with authentication
- Learn about middleware for request logging