================================================================================ CRUNCHYROLL DOWNLOADER - CHALLENGE 2 SOLUTION Chapter 3: JWT Login & Authentication Challenge: Token Caching ================================================================================ PROBLEM: Implement save_token() and load_cached_token() to avoid re-logging in. SOLUTION: ================================================================================ File: crunchyroll_utils.py (token caching) import json from pathlib import Path from datetime import datetime def save_token(token: str, cache_file: str): """Save JWT token to file for reuse.""" print(f"💾 Saving token to cache...") data = { "token": token, "timestamp": datetime.now().isoformat(), "note": "Do not commit this file to Git!" } try: with open(cache_file, 'w') as f: json.dump(data, f, indent=2) print(f"✅ Token cached to {cache_file}") return True except Exception as e: print(f"❌ Error saving token: {e}") return False def load_cached_token(cache_file: str) -> str: """Load cached JWT token if it exists.""" cache_path = Path(cache_file) # Check if cache file exists if not cache_path.exists(): print(f"â„šī¸ No cached token found") return None try: with open(cache_file, 'r') as f: data = json.load(f) token = data.get("token") saved_at = data.get("timestamp") if token: print(f"✅ Loaded cached token") if saved_at: print(f" Saved at: {saved_at}") return token else: print(f"❌ No token in cache file") return None except json.JSONDecodeError: print(f"❌ Cache file corrupted (invalid JSON)") return None except Exception as e: print(f"❌ Error loading token: {e}") return None WHY TOKEN CACHING? ================================================================================ WITHOUT caching: - Every run: Login → Get token → Use it - Slow (login request takes time) - More requests to server - User sees login delay WITH caching: - First run: Login → Save token → Use it - Later runs: Load token → Use it (no login!) - Fast (skip login step) - Fewer server requests - Better user experience HOW IT WORKS ================================================================================ First Run: ┌─────────────────────────────────────────┐ │ 1. User runs: python download.py │ │ 2. Check for token_cache.json - NO │ │ 3. Must login (username/password) │ │ 4. Get JWT token from server │ │ 5. Save token to token_cache.json │ │ 6. Use token to fetch episodes │ └─────────────────────────────────────────┘ ↓ (takes ~5 seconds) Second Run (same day): ┌─────────────────────────────────────────┐ │ 1. User runs: python download.py │ │ 2. Check for token_cache.json - YES! │ │ 3. Load token from file │ │ 4. Use token to fetch episodes │ │ 5. Done! (skip login) │ └─────────────────────────────────────────┘ ↓ (takes ~1 second!) CACHE FILE FORMAT ================================================================================ token_cache.json: { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "timestamp": "2026-06-30T12:34:56.789012", "note": "Do not commit this file to Git!" } USAGE IN MAIN SCRIPT ================================================================================ from config import Config from crunchyroll_utils import login, load_cached_token, save_token config = Config() if not config.validate(): exit(1) # Try to load cached token token = load_cached_token(config.token_cache) # If no cache, login if not token: print("🔐 No cached token, logging in...") token = login(config) if not token: exit(1) # Save it for next time save_token(token, config.token_cache) # Now use token for API requests print("✅ Ready to fetch episodes!") WHEN CACHE EXPIRES ================================================================================ JWT tokens have expiration (usually 1 hour or 1 day) If you get 401 error: - Token expired - Delete token_cache.json - Re-run script (will login again) Example: ❌ 401 Unauthorized → Delete token_cache.json → python download.py → ✅ Re-login, save new token This is automatic handling of token refresh! SECURITY NOTE ================================================================================ âš ī¸ IMPORTANT: - token_cache.json contains your JWT token - It's like a temporary password file - Must be in .gitignore (don't commit!) - If someone gets it, they can access your account - Tokens expire anyway (1 hour/1 day) - If compromised, just delete it and re-login ================================================================================