================================================================================ CRUNCHYROLL DOWNLOADER - CHALLENGE 3 SOLUTION Chapter 3: JWT Login & Authentication Challenge: Authenticated Requests ================================================================================ PROBLEM: Create get_with_token() that includes JWT in Authorization header. SOLUTION: ================================================================================ File: crunchyroll_utils.py (authenticated requests) import requests def get_with_token(url: str, token: str, timeout: int = 10) -> dict: """Make GET request with JWT token.""" # Create headers with JWT token headers = { "Authorization": f"Bearer {token}", "User-Agent": "Mozilla/5.0", "Content-Type": "application/json" } try: print(f"📡 Fetching: {url}") response = requests.get(url, headers=headers, timeout=timeout) # Handle different status codes if response.status_code == 200: print(f"✅ Success (200)") return response.json() elif response.status_code == 401: print(f"❌ Unauthorized (401) - token expired or invalid") return None elif response.status_code == 403: print(f"❌ Forbidden (403) - no permission") return None elif response.status_code == 404: print(f"❌ Not found (404) - endpoint doesn't exist") return None elif response.status_code == 429: print(f"⚠️ Rate limited (429) - wait before retrying") return None else: print(f"⚠️ Unexpected status: {response.status_code}") return None except requests.exceptions.Timeout: print(f"❌ Request timeout (took longer than {timeout}s)") return None except requests.exceptions.ConnectionError: print(f"❌ Connection error - no internet or server down") return None except requests.exceptions.JSONDecodeError: print(f"❌ Invalid JSON in response") return None except Exception as e: print(f"❌ Unexpected error: {e}") return None def post_with_token(url: str, token: str, data: dict = None, timeout: int = 10) -> dict: """Make POST request with JWT token.""" headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } try: print(f"📤 Posting to: {url}") response = requests.post(url, headers=headers, json=data, timeout=timeout) if response.status_code in [200, 201]: print(f"✅ Success ({response.status_code})") return response.json() elif response.status_code == 401: print(f"❌ Unauthorized (401)") return None else: print(f"⚠️ Status: {response.status_code}") return None except Exception as e: print(f"❌ Error: {e}") return None USAGE EXAMPLES ================================================================================ # Example 1: Get episodes from crunchyroll_utils import get_with_token token = "eyJhbGciOiJIUzI1NiIs..." url = "https://api.crunchyroll.com/api/v2/episodes" episodes = get_with_token(url, token) if episodes: print(f"Got {len(episodes)} episodes") # Example 2: Complete workflow from config import Config from crunchyroll_utils import login, load_cached_token, save_token, get_with_token config = Config() token = load_cached_token(config.token_cache) if not token: token = login(config) save_token(token, config.token_cache) # Now fetch data episodes = get_with_token( f"{config.api_base}/api/v2/episodes", token ) THE BEARER TOKEN HEADER ================================================================================ Header format: Authorization: Bearer {token} Note: - Must have "Bearer " prefix (with space) - Token comes after the space - Case-sensitive: "Bearer" (capital B) Examples: ✅ CORRECT: Authorization: Bearer eyJhbGciOiJIUzI1NiIs... ❌ WRONG: Authorization: eyJhbGciOiJIUzI1NiIs... (missing "Bearer ") Authorization: bearer eyJhbGciOiJIUzI1NiIs... (lowercase b) Authorization: Bearer eyJhbGciOiJIUzI1NiIs... (extra space) COMPLETE JWT REQUEST FLOW ================================================================================ 1. CLIENT: Construct request with Authorization header GET /api/v2/episodes Authorization: Bearer eyJhbGciOiJIUzI1NiIs... 2. SERVER: Receive request - Extract token from header - Verify token signature - Check if token expired - Extract user from token payload 3. SERVER: Process request - Is user allowed to access /api/v2/episodes? - Get episodes for that user - Return data 4. CLIENT: Receive response - 200 OK: Process episodes - 401: Token invalid/expired, re-login - 403: User doesn't have permission - 500: Server error KEY POINTS ================================================================================ ✅ Authorization header: "Bearer {token}" ✅ Token goes in HEADER (not URL or body) ✅ Different endpoints may need different tokens ✅ Handle 401 errors (re-login) ✅ Handle network errors (timeout, connection) ✅ Check status codes (200, 401, 403, 429, etc) This is the foundation of JWT-based API access! ================================================================================