================================================================================ CRUNCHYROLL DOWNLOADER - CHALLENGE 2 SOLUTION Chapter 1: JWT & API Setup Challenge: JWT Decoding ================================================================================ PROBLEM: Understand JWT structure: 1. Get a JWT token string 2. Decode the payload (base64 decode middle part) 3. Extract user information 4. Check expiration timestamp SOLUTION: ================================================================================ File: test_jwt_decode.py import base64 import json from datetime import datetime def decode_jwt(token: str) -> dict: """Decode JWT payload (NOT for verification - just reading).""" print("=" * 60) print("JWT DECODER") print("=" * 60) # Split JWT into 3 parts parts = token.split('.') if len(parts) != 3: print("āŒ Invalid JWT format (must have 3 parts)") return None header_part, payload_part, signature_part = parts print(f"\nšŸ“‹ JWT STRUCTURE:") print(f" Header: {header_part[:30]}...") print(f" Payload: {payload_part[:30]}...") print(f" Signature: {signature_part[:30]}...") # Decode header try: # Add padding if needed (base64 requirement) header_padding = header_part + '=' * (4 - len(header_part) % 4) header_decoded = base64.urlsafe_b64decode(header_padding) header = json.loads(header_decoded) print(f"\nāœ… Header decoded:") print(f" Algorithm: {header.get('alg')}") print(f" Type: {header.get('typ')}") except Exception as e: print(f"āŒ Error decoding header: {e}") return None # Decode payload try: payload_padding = payload_part + '=' * (4 - len(payload_part) % 4) payload_decoded = base64.urlsafe_b64decode(payload_padding) payload = json.loads(payload_decoded) print(f"\nāœ… Payload decoded:") for key, value in payload.items(): if key == 'exp': # Convert Unix timestamp to readable date exp_date = datetime.fromtimestamp(value) print(f" {key}: {value} ({exp_date})") else: print(f" {key}: {value}") except Exception as e: print(f"āŒ Error decoding payload: {e}") return None # Check expiration if 'exp' in payload: exp_timestamp = payload['exp'] now_timestamp = datetime.now().timestamp() if now_timestamp > exp_timestamp: print(f"\nāš ļø TOKEN EXPIRED!") else: remaining = int(exp_timestamp - now_timestamp) print(f"\nāœ… Token valid for {remaining} more seconds") print(f"\nāš ļø Note: This only READS the token.") print(f" Signature verification requires the secret key.") return payload # Test with sample token if __name__ == "__main__": # Real JWT from Crunchyroll login would look like: sample_token = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." "eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJ1c2VyQGNydW5jaHlyb2xsLmNvbSIsImV4cCI6MTY4ODAwMDAwMH0." "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" ) payload = decode_jwt(sample_token) if payload: print(f"\nāœ… Successfully decoded!") print(f" Email: {payload.get('email')}") OUTPUT EXAMPLE ================================================================================ ============================================================ JWT DECODER ============================================================ šŸ“‹ JWT STRUCTURE: Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Payload: eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJ1c2... Signature: TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ... āœ… Header decoded: Algorithm: HS256 Type: JWT āœ… Payload decoded: sub: 1234567890 email: user@crunchyroll.com exp: 1688000000 (2023-06-29 12:26:40) āœ… Token valid for 3599 more seconds āš ļø Note: This only READS the token. Signature verification requires the secret key. āœ… Successfully decoded! Email: user@crunchyroll.com KEY CONCEPTS ================================================================================ JWT has 3 parts: 1. HEADER (encoded as base64): {"alg":"HS256","typ":"JWT"} - Says what algorithm was used - Always "JWT" type 2. PAYLOAD (encoded as base64): {"sub":"1234567890","email":"user@email.com","exp":1688000000} - This is YOUR data - "exp" is expiration timestamp (Unix time) - Can contain any fields server wants 3. SIGNATURE (not base64): TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ - Server's cryptographic signature - Proves token wasn't tampered with - Can only verify WITH the server's secret key WHY DECODE? ================================================================================ āœ… Know when token expires (don't call API after expiration) āœ… Get user information without another API call āœ… Check permissions/roles āœ… Debug token issues āŒ Can't verify signature without server's secret āŒ Token can't be modified (signature would fail) āŒ If you see data you don't expect, token may be fake REAL-WORLD USAGE ================================================================================ # In your code: from test_jwt_decode import decode_jwt from crunchyroll_utils import login token = login(config) payload = decode_jwt(token) if payload and payload.get('email'): print(f"Logged in as: {payload['email']}") ================================================================================