RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Fredoline Eruo
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /AI-Powered SaaS Products
  6. /Ch. 5
AI-Powered SaaS Products

05. Authentication

Chapter 5 of 24 · 20 min
KEY INSIGHT

SaaS authentication requires balancing security with usability—email/password for humans, API keys for machines, and careful handling of token lifecycle across organization membership changes. Authentication in AI SaaS serves two distinct clients: humans accessing dashboards and programmatic clients using APIs. Each has different security requirements and threat models. Email/password authentication for humans should use established patterns. Bcrypt or Argon2 for password hashing, secure session management, and protection against timing attacks. ```python from passlib.context import CryptContext from itsdangerous import URLSafeTimedSerializer import secrets pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") class AuthService: def __init__(self, secret_key: str): self.serializer = URLSafeTimedSerializer(secret_key) def hash_password(self, password: str) -> str: return pwd_context.hash(password) def verify_password(self, plain_password: str, hashed: str) -> bool: return pwd_context.verify(plain_password, hashed) def create_session_token(self, user_id: str, tenant_id: str) -> str: """Create a signed session token with embedded claims.""" payload = { "sub": user_id, "tenant_id": tenant_id, "jti": secrets.token_urlsafe(16), # Token ID for revocation "type": "session" } return self.serializer.dumps(payload) def validate_session(self, token: str, max_age: int = 86400) -> dict: """Validate and decode session token.""" try: payload = self.serializer.loads(token, max_age=max_age) if payload.get("type") != "session": raise ValueError("Invalid token type") return payload except Exception as e: raise ValueError(f"Token validation failed: {e}") ``` API key authentication for machine clients needs special handling. API keys must be stored as hashed values with the plaintext returned only once during creation. ```python import hashlib import secrets class ApiKeyService: def __init__(self, db: Session): self.db = db def generate_api_key(self, workspace_id: str, name: str) -> tuple[str, str]: """Generate a new API key. Returns (key_id, secret_hash). The plaintext key is returned ONLY here—store the hash. """ key_id = f"sk_{secrets.token_urlsafe(8)}" secret = secrets.token_urlsafe(32) secret_hash = hashlib.sha256(secret.encode()).hexdigest() api_key = ApiKey( id=key_id, workspace_id=workspace_id, name=name, key_hash=secret_hash, created_at=datetime.utcnow() ) self.db.add(api_key) self.db.commit() # Return the full key for the user to copy return f"{key_id}_{secret}", secret_hash def validate_api_key(self, full_key: str) -> ApiKey: """Validate an API key and return the associated record.""" if "_" not in full_key: raise ValueError("Invalid API key format") key_id, secret = full_key.split("_", 1) secret_hash = hashlib.sha256(secret.encode()).hexdigest() api_key = self.db.query(ApiKey).filter_by( id=key_id, key_hash=secret_hash, is_active=True ).first() if not api_key: raise ValueError("Invalid or inactive API key") return api_key ``` A common failure: not invalidating sessions when a user's organization membership changes. If an employee is removed from an organization, their session tokens must be revoked. Track token IDs in a blacklist or use short-lived tokens with refresh mechanisms.

EXERCISE

Implement API key rotation without downtime. The system should allow creating a new key, validating against both old and new during a grace period, then invalidating the old key. Handle the case where the user loses access to the old key during rotation.

← Chapter 4
User Management
Chapter 6 →
Authorization