šŸ” HTTP Response & API Authentication

Understanding Response Structure and Authentication Methods

šŸ“¦ 2.6 Identify the Parts of an HTTP Response

What is an HTTP Response?

An HTTP response is the message sent by a server back to a client after receiving and processing an HTTP request. Every HTTP response consists of three main parts: Status Code, Headers, and Body.

graph TD A["HTTP Response"] --> B["Status Line"] A --> C["Response Headers"] A --> D["Response Body"] B --> B1["HTTP Version"] B --> B2["Status Code"] B --> B3["Status Message"] C --> C1["Content-Type"] C --> C2["Content-Length"] C --> C3["Cache-Control"] C --> C4["Set-Cookie"] D --> D1["HTML"] D --> D2["JSON"] D --> D3["XML"] D --> D4["Plain Text"] style A fill:#667eea,stroke:#764ba2,stroke-width:3px,color:#fff style B fill:#ffa726,stroke:#e65100,stroke-width:2px,color:#fff style C fill:#66bb6a,stroke:#2e7d32,stroke-width:2px,color:#fff style D fill:#42a5f5,stroke:#1976d2,stroke-width:2px,color:#fff

Part 1: Status Code (Status Line)

The status line is the first line of an HTTP response and contains three elements:

Status Line Format

HTTP/1.1 200 OK

Components:

  • HTTP/1.1 - HTTP protocol version
  • 200 - Status code (3-digit number)
  • OK - Status message (human-readable description)
Status Code Category Range Meaning Examples
Informational 1xx Request received, processing continues 100 Continue, 101 Switching Protocols
Success 2xx Request successfully processed 200 OK, 201 Created, 204 No Content
Redirection 3xx Further action needed 301 Moved Permanently, 302 Found
Client Error 4xx Client-side error in request 400 Bad Request, 401 Unauthorized, 404 Not Found
Server Error 5xx Server failed to fulfill request 500 Internal Server Error, 503 Service Unavailable

Part 2: Response Headers

Response headers provide metadata about the response. They contain key-value pairs that give additional information about the server, the response data, and how the client should handle it.

Common Response Headers

Header Name Purpose Example Value
Content-Type Specifies format of response body application/json
text/html
application/xml
Content-Length Size of response body in bytes 1234
Content-Encoding Compression method used gzip, deflate, br
Cache-Control Caching directives no-cache
max-age=3600
public
Set-Cookie Set cookies on client sessionId=abc123; Path=/
Server Information about server software Apache/2.4.1
nginx/1.18.0
Date Timestamp when response was sent Wed, 21 Oct 2024 07:28:00 GMT
Location URL for redirect or new resource https://api.example.com/users/123
X-RateLimit-Limit Maximum requests allowed 1000
X-RateLimit-Remaining Requests remaining in time window 999

Part 3: Response Body

The response body contains the actual data being sent back to the client. The format depends on the Content-Type header.

Common Response Body Formats

1. JSON (JavaScript Object Notation)

Most common format for REST APIs

{
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com",
    "active": true,
    "roles": ["user", "admin"],
    "created_at": "2024-01-15T10:30:00Z"
}

Content-Type: application/json

2. XML (Extensible Markup Language)

Structured format, common in SOAP APIs

<?xml version="1.0" encoding="UTF-8"?>
<user>
    <id>123</id>
    <name>John Doe</name>
    <email>john@example.com</email>
    <active>true</active>
</user>

Content-Type: application/xml or text/xml

3. HTML (HyperText Markup Language)

Used for web pages

<!DOCTYPE html>
<html>
<head>
    <title>Welcome</title>
</head>
<body>
    <h1>Hello, World!</h1>
</body>
</html>

Content-Type: text/html

4. Plain Text

Simple text data

Hello, World!
This is plain text response.

Content-Type: text/plain

Complete HTTP Response Example

šŸ“„ Full HTTP Response Structure:
HTTP/1.1 200 OK                              ← Status Line
Date: Wed, 21 Oct 2024 07:28:00 GMT          ← Headers start
Server: nginx/1.18.0
Content-Type: application/json
Content-Length: 154
Cache-Control: max-age=3600
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
Connection: keep-alive                        ← Headers end
                                              ← Blank line separates headers from body
{                                             ← Body starts
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com",
    "status": "active"
}                                             ← Body ends

Python Example: Parsing HTTP Response Parts

#!/usr/bin/env python3
"""
HTTP Response Parts Analysis
Demonstrates how to access and parse each part of an HTTP response
"""

import requests
import json


def analyze_http_response(url):
    """
    Make HTTP request and analyze all parts of the response
    """
    print("=" * 70)
    print(f"Analyzing HTTP Response from: {url}")
    print("=" * 70)
    
    # Make the request
    response = requests.get(url)
    
    # ========================================================================
    # PART 1: STATUS CODE (STATUS LINE)
    # ========================================================================
    print("\nšŸ“Š PART 1: STATUS LINE")
    print("-" * 70)
    print(f"HTTP Version:    HTTP/1.1 (default)")
    print(f"Status Code:     {response.status_code}")
    print(f"Status Message:  {response.reason}")
    print(f"Success:         {response.ok}")  # True if status < 400
    
    # ========================================================================
    # PART 2: RESPONSE HEADERS
    # ========================================================================
    print("\nšŸ“‹ PART 2: RESPONSE HEADERS")
    print("-" * 70)
    
    # Display all headers
    print("All Headers:")
    for header, value in response.headers.items():
        print(f"  {header}: {value}")
    
    # Access specific important headers
    print("\nKey Headers:")
    print(f"  Content-Type:     {response.headers.get('Content-Type', 'N/A')}")
    print(f"  Content-Length:   {response.headers.get('Content-Length', 'N/A')}")
    print(f"  Server:           {response.headers.get('Server', 'N/A')}")
    print(f"  Date:             {response.headers.get('Date', 'N/A')}")
    print(f"  Cache-Control:    {response.headers.get('Cache-Control', 'N/A')}")
    
    # Check for rate limiting headers
    if 'X-RateLimit-Limit' in response.headers:
        print(f"\nRate Limiting Info:")
        print(f"  Limit:     {response.headers.get('X-RateLimit-Limit')}")
        print(f"  Remaining: {response.headers.get('X-RateLimit-Remaining')}")
        print(f"  Reset:     {response.headers.get('X-RateLimit-Reset')}")
    
    # ========================================================================
    # PART 3: RESPONSE BODY
    # ========================================================================
    print("\nšŸ“¦ PART 3: RESPONSE BODY")
    print("-" * 70)
    
    # Determine content type
    content_type = response.headers.get('Content-Type', '')
    print(f"Content-Type: {content_type}")
    
    # Parse based on content type
    if 'application/json' in content_type:
        print("\nBody Format: JSON")
        try:
            data = response.json()
            print("Parsed JSON:")
            print(json.dumps(data, indent=2))
            
            # Access JSON data
            print("\nAccessing JSON Fields:")
            if isinstance(data, dict):
                for key in list(data.keys())[:5]:  # Show first 5 keys
                    print(f"  {key}: {data[key]}")
        except json.JSONDecodeError:
            print("Failed to parse JSON")
            print(response.text[:500])
    
    elif 'text/html' in content_type:
        print("\nBody Format: HTML")
        print("HTML Content (first 500 chars):")
        print(response.text[:500])
    
    elif 'text/plain' in content_type:
        print("\nBody Format: Plain Text")
        print(response.text)
    
    else:
        print("\nBody Format: Other/Binary")
        print(f"Body Size: {len(response.content)} bytes")
        print("Raw content (first 200 chars):")
        print(response.text[:200])
    
    # ========================================================================
    # ADDITIONAL RESPONSE PROPERTIES
    # ========================================================================
    print("\n⚔ ADDITIONAL PROPERTIES")
    print("-" * 70)
    print(f"Encoding:      {response.encoding}")
    print(f"URL:           {response.url}")
    print(f"Elapsed Time:  {response.elapsed.total_seconds():.3f} seconds")
    print(f"Cookies:       {dict(response.cookies)}")
    
    return response


# =============================================================================
# EXAMPLE USAGE
# =============================================================================

if __name__ == "__main__":
    # Example 1: JSON API Response
    print("\n" + "="*70)
    print("EXAMPLE 1: Analyzing JSON API Response")
    print("="*70)
    analyze_http_response("https://jsonplaceholder.typicode.com/users/1")
    
    input("\n\nPress Enter to see another example...")
    
    # Example 2: Different endpoint
    print("\n" + "="*70)
    print("EXAMPLE 2: Analyzing List Response")
    print("="*70)
    analyze_http_response("https://jsonplaceholder.typicode.com/posts?_limit=3")
āœ… Key Takeaways:
  • Status Code: Tells you if request succeeded (2xx), failed (4xx/5xx), or needs action (3xx)
  • Headers: Provide metadata - content type, encoding, caching, rate limits, etc.
  • Body: Contains actual data - format specified by Content-Type header
  • Always check all three parts when debugging API issues

šŸ” 2.7 Utilize Common API Authentication Mechanisms

What is API Authentication?

API authentication is the process of verifying the identity of a client making requests to an API. It ensures that only authorized users or applications can access protected resources.

graph LR A["Client"] --> B["Authentication Method"] B --> C["Basic Auth"] B --> D["Custom Token"] B --> E["API Key"] C --> F["Username + Password"] D --> G["Bearer Token"] E --> H["API Key Header"] F --> I["API Server"] G --> I H --> I I --> J["Verify Credentials"] J --> K["Grant Access"] style A fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000 style C fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#000 style D fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px,color:#000 style E fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000 style I fill:#fff3cd,stroke:#ffc107,stroke-width:2px,color:#000 style K fill:#d4edda,stroke:#28a745,stroke-width:2px,color:#000

1. Basic Authentication

Basic Auth Username and Password

Basic Authentication is the simplest form of HTTP authentication. It sends a username and password encoded in Base64 in the Authorization header.

How it Works:
  1. Client combines username and password with a colon: username:password
  2. Encodes the string in Base64
  3. Sends in Authorization header: Authorization: Basic <base64-string>
  4. Server decodes and verifies credentials

Format

# Header Format
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

# Breakdown:
# "username:password" -> Base64 encode -> "dXNlcm5hbWU6cGFzc3dvcmQ="

Python Implementation

import requests
from requests.auth import HTTPBasicAuth
import base64

# Method 1: Using HTTPBasicAuth (Recommended)
response = requests.get(
    'https://api.example.com/data',
    auth=HTTPBasicAuth('username', 'password')
)

# Method 2: Using auth tuple (shorthand)
response = requests.get(
    'https://api.example.com/data',
    auth=('username', 'password')
)

# Method 3: Manual header construction
username = 'username'
password = 'password'
credentials = f"{username}:{password}"
encoded = base64.b64encode(credentials.encode()).decode()

headers = {
    'Authorization': f'Basic {encoded}'
}
response = requests.get('https://api.example.com/data', headers=headers)

print(f"Status: {response.status_code}")
print(f"Data: {response.json()}")
āš ļø Security Considerations:
  • Not Encrypted: Base64 encoding is NOT encryption - credentials are easily decoded
  • Must use HTTPS: Always use Basic Auth over HTTPS/TLS to encrypt transmission
  • Not Recommended: Use only for simple applications or internal APIs
  • No Expiration: Credentials remain valid until changed
Pros Cons
āœ… Simple to implement
āœ… Built into HTTP standard
āœ… Widely supported
āŒ Less secure (if not over HTTPS)
āŒ Credentials sent with every request
āŒ No token expiration
āŒ Hard to revoke access

2. Custom Token Authentication (Bearer Token)

Bearer Token Token-Based Authentication

Token authentication uses a unique token (usually a JSON Web Token - JWT) that is issued after successful login. The token is sent in the Authorization header with the Bearer prefix.

How it Works:
  1. Client sends login credentials to authentication endpoint
  2. Server validates credentials and issues a token (often JWT)
  3. Client stores token securely
  4. Client includes token in Authorization header for subsequent requests
  5. Server validates token and grants access if valid

Format

# Header Format
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjN9.xyzABC123

# JWT Token Structure (3 parts separated by dots):
# 1. Header (algorithm, token type)
# 2. Payload (user data, expiration)
# 3. Signature (verifies token integrity)

Python Implementation

import requests

# Step 1: Login to get token
login_url = "https://api.example.com/auth/login"
credentials = {
    "username": "user@example.com",
    "password": "secretpassword"
}

# Get token from login endpoint
login_response = requests.post(login_url, json=credentials)
token = login_response.json()['access_token']

print(f"Received token: {token[:50]}...")

# Step 2: Use token for API requests
headers = {
    'Authorization': f'Bearer {token}',
    'Content-Type': 'application/json'
}

# Make authenticated request
response = requests.get(
    'https://api.example.com/api/user/profile',
    headers=headers
)

if response.status_code == 200:
    print("āœ… Authentication successful!")
    print(f"User data: {response.json()}")
elif response.status_code == 401:
    print("āŒ Token expired or invalid")
else:
    print(f"āŒ Error: {response.status_code}")


# =============================================================================
# TOKEN REFRESH EXAMPLE
# =============================================================================

def make_authenticated_request(url, token):
    """Make request with token, handle refresh if needed"""
    headers = {'Authorization': f'Bearer {token}'}
    response = requests.get(url, headers=headers)
    
    if response.status_code == 401:
        # Token expired, refresh it
        print("Token expired, refreshing...")
        new_token = refresh_token(token)
        headers['Authorization'] = f'Bearer {new_token}'
        response = requests.get(url, headers=headers)
    
    return response

def refresh_token(old_token):
    """Request new token using refresh token"""
    refresh_url = "https://api.example.com/auth/refresh"
    response = requests.post(
        refresh_url,
        headers={'Authorization': f'Bearer {old_token}'}
    )
    return response.json()['access_token']
āœ… JWT Token Benefits:
  • Stateless: Server doesn't need to store tokens - validates using signature
  • Self-contained: Token includes user information and expiration
  • Expiration: Tokens expire automatically for security
  • Revocable: Can be invalidated if compromised
  • Cross-domain: Works across different domains/services
Pros Cons
āœ… More secure than Basic Auth
āœ… Tokens expire automatically
āœ… Can be revoked
āœ… Stateless (scalable)
āœ… Can store user data in token
āŒ More complex to implement
āŒ Requires token management
āŒ Token refresh logic needed
āŒ Larger header size

3. API Key Authentication

API Key Key-Based Authentication

API Key authentication uses a unique key assigned to each client. The key is sent in a custom header or as a query parameter with each request.

How it Works:
  1. Developer registers application and receives API key
  2. API key is stored securely by client
  3. Client includes API key in header or query parameter
  4. Server validates key and grants access if valid
  5. Server may track usage, rate limits per key

Common Formats

# Method 1: Custom Header (Most Common)
X-API-Key: abc123def456ghi789jkl
Api-Key: abc123def456ghi789jkl
apikey: abc123def456ghi789jkl

# Method 2: Authorization Header
Authorization: ApiKey abc123def456ghi789jkl

# Method 3: Query Parameter (Less Secure)
https://api.example.com/data?api_key=abc123def456ghi789jkl
https://api.example.com/data?apikey=abc123def456ghi789jkl

Python Implementation

import requests
import os

# Best practice: Store API key in environment variable
API_KEY = os.getenv('API_KEY', 'your-api-key-here')

# Method 1: Using custom header (Most Common)
headers = {
    'X-API-Key': API_KEY,
    'Content-Type': 'application/json'
}

response = requests.get(
    'https://api.example.com/data',
    headers=headers
)

# Method 2: Using Authorization header
headers = {
    'Authorization': f'ApiKey {API_KEY}'
}

response = requests.get(
    'https://api.example.com/data',
    headers=headers
)

# Method 3: Using query parameter (Not recommended - visible in logs)
params = {
    'api_key': API_KEY
}

response = requests.get(
    'https://api.example.com/data',
    params=params
)

print(f"Status: {response.status_code}")
print(f"Data: {response.json()}")


# =============================================================================
# REAL-WORLD EXAMPLES
# =============================================================================

# Example 1: OpenWeatherMap API
def get_weather(city):
    """Get weather data using API key"""
    api_key = os.getenv('OPENWEATHER_API_KEY')
    url = "https://api.openweathermap.org/data/2.5/weather"
    
    params = {
        'q': city,
        'appid': api_key,
        'units': 'metric'
    }
    
    response = requests.get(url, params=params)
    
    if response.status_code == 200:
        data = response.json()
        return {
            'city': data['name'],
            'temperature': data['main']['temp'],
            'description': data['weather'][0]['description']
        }
    else:
        return {'error': f'Status {response.status_code}'}


# Example 2: GitHub API
def get_github_user(username):
    """Get GitHub user info using API key"""
    token = os.getenv('GITHUB_TOKEN')
    url = f"https://api.github.com/users/{username}"
    
    headers = {
        'Authorization': f'token {token}',
        'Accept': 'application/vnd.github.v3+json'
    }
    
    response = requests.get(url, headers=headers)
    
    if response.status_code == 200:
        data = response.json()
        return {
            'name': data['name'],
            'followers': data['followers'],
            'public_repos': data['public_repos']
        }
    else:
        return {'error': f'Status {response.status_code}'}


# Example 3: Rate limiting with API key
def make_api_request_with_limits(url, api_key):
    """Make API request and check rate limits"""
    headers = {'X-API-Key': api_key}
    response = requests.get(url, headers=headers)
    
    # Check rate limit headers
    limit = response.headers.get('X-RateLimit-Limit')
    remaining = response.headers.get('X-RateLimit-Remaining')
    reset = response.headers.get('X-RateLimit-Reset')
    
    print(f"Rate Limit: {remaining}/{limit} requests remaining")
    print(f"Resets at: {reset}")
    
    if response.status_code == 429:
        print("āŒ Rate limit exceeded!")
        retry_after = response.headers.get('Retry-After')
        print(f"Retry after: {retry_after} seconds")
    
    return response
āš ļø API Key Security Best Practices:
  • Never hardcode: Store API keys in environment variables or secure vaults
  • Never commit to Git: Add to .gitignore, use .env files
  • Use headers, not URLs: Query parameters are logged and cached
  • Rotate regularly: Change API keys periodically
  • Use different keys: Separate keys for development, staging, production
  • Monitor usage: Track API key usage to detect abuse
Pros Cons
āœ… Simple to implement
āœ… Easy to track usage per client
āœ… Good for rate limiting
āœ… Can revoke individual keys
āœ… No login required
āŒ No user-level authentication
āŒ Keys don't expire automatically
āŒ If leaked, harder to detect
āŒ Less secure than OAuth

Comparison of Authentication Methods

Feature Basic Auth Bearer Token (JWT) API Key
Security āš ļø Low (if not HTTPS) āœ… High āš ļø Medium
Complexity āœ… Very Simple āš ļø Complex āœ… Simple
Expiration āŒ No āœ… Yes (auto) āŒ No (manual)
User Identity āœ… Yes āœ… Yes āŒ No (app-level)
Rate Limiting āš ļø By user āš ļø By user āœ… By key
Revocation āš ļø Change password āœ… Easy āœ… Easy
Use Case Internal APIs, simple apps User authentication, mobile apps Third-party integrations, public APIs
šŸ’” When to Use Each Method:
  • Basic Auth: Internal tools, development/testing, simple applications
  • Bearer Token (JWT): User-facing applications, mobile apps, single-page apps (SPAs)
  • API Key: Third-party integrations, public APIs, service-to-service communication

šŸ Interactive Python Interpreter

šŸ’” How to use:
  • Write Python code in the editor below
  • Click "ā–¶ Run Code" to execute
  • Output appears in the console below
  • Use sample codes for quick testing
  • Experiment with response parsing and authentication examples
šŸ“ Python Code Editor
⚔ Console Output
Ready to execute Python code...
āš ļø Note: This is a client-side Python interpreter using Skulpt. Network requests are simulated for demonstration purposes.