================================================================================ CRUNCHYROLL DOWNLOADER - CHALLENGE 1 SOLUTION Chapter 3: JWT Login & Authentication Challenge: Implement Login ================================================================================ THIS IS THE CORE OF JWT AUTHENTICATION! PROBLEM: Create login() function that authenticates with Crunchyroll and returns JWT token. SOLUTION: ================================================================================ File: crunchyroll_utils.py import requests import json from pathlib import Path from config import Config def login(config: Config) -> str: """Login to Crunchyroll and get JWT token.""" print("🔐 Authenticating with Crunchyroll...") # Crunchyroll login endpoint url = f"{config.api_base}/auth/v1/authenticate" # Prepare login payload payload = { "username": config.email, "password": config.password, "grant_type": "password" } try: # POST request with credentials response = requests.post(url, json=payload, timeout=10) # Check for errors if response.status_code != 200: print(f"❌ Login failed: {response.status_code}") print(f" Response: {response.text}") return None # Extract JWT token from response data = response.json() token = data.get('access_token') if not token: print("❌ No token in response!") print(f" Response: {data}") return None print("✅ Login successful!") print(f" Token: {token[:50]}...") # Show first 50 chars return token except requests.exceptions.Timeout: print("❌ Login timeout - server not responding") return None except requests.exceptions.ConnectionError: print("❌ Connection error - check internet") return None except Exception as e: print(f"❌ Unexpected error: {e}") return None HOW JWT LOGIN WORKS ================================================================================ Step 1: SEND CREDENTIALS POST /auth/v1/authenticate Body: { "username": "emubantam@gmail.com", "password": "mypassword123", "grant_type": "password" } Step 2: SERVER VALIDATES - Checks if username exists - Checks if password matches - If valid, creates JWT token Step 3: SERVER RESPONDS 200 OK: { "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600 } Step 4: EXTRACT TOKEN token = data.get('access_token') Returns: "eyJhbGciOiJIUzI1NiIs..." Step 5: USE TOKEN IN FUTURE REQUESTS Headers: { "Authorization": "Bearer eyJhbGciOiJIUzI1NiIs..." } JWT TOKEN STRUCTURE ================================================================================ A JWT token has 3 parts separated by dots: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIn0. TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ Part 1 (Header): {"alg":"HS256","typ":"JWT"} Part 2 (Payload): {"sub":"1234567890","email":"john@example.com"} Part 3 (Signature): Server's signature to verify it's legitimate ERROR HANDLING ================================================================================ Common errors and solutions: 401 Unauthorized: → Wrong username or password → Check .env file for typos 403 Forbidden: → Account suspended or locked → Try logging in on website first 400 Bad Request: → Missing or malformed payload → Check email/password format Network Error: → Internet not connected → API server down Timeout: → Server taking too long → Check internet speed TESTING YOUR LOGIN ================================================================================ Test script: from config import Config from crunchyroll_utils import login config = Config() if config.validate(): token = login(config) if token: print(f"✅ Successfully logged in!") print(f"Token: {token[:50]}...") else: print("❌ Login failed") else: print("❌ Config invalid") SECURITY NOTE ================================================================================ ⚠️ IMPORTANT: - token_cache.json will store your JWT token - JWT tokens are like passwords - keep them secret! - Never commit token_cache.json to Git - If token is exposed, Crunchyroll can revoke it - Tokens expire (usually in 1 hour) - Can be refreshed without re-logging in ================================================================================