OAuth2 Python Assignment#
Learning Goals#
Students will:
Understand OAuth2 Authorization Code Flow
Build a local HTTP server using only
http.serverManually construct authorization URLs
Exchange authorization codes for access tokens
Call protected APIs using tokens
Assignment Specification#
You must implement OAuth2 login with GitHub using pure Python.
Requirements:
Start a local HTTP server on port 8000
Open the browser automatically to the OAuth2 authorization URL
Receive the
codeparameter on redirectExchange the code for an access token
Call the GitHub API
/userendpointPrint the user profile data to the console
Starter Code (Complete Minimal Example)#
Save this as oauth2_github.py.
import http.server
import socketserver
import webbrowser
import requests
import threading
import urllib.parse
import os
CLIENT_ID = os.getenv("CLIENT_ID") or "YOUR_CLIENT_ID"
CLIENT_SECRET = os.getenv("CLIENT_SECRET") or "YOUR_CLIENT_SECRET"
REDIRECT_URI = "http://localhost:8000/callback"
AUTH_URL = "https://github.com/login/oauth/authorize"
TOKEN_URL = "https://github.com/login/oauth/access_token"
USER_API_URL = "https://api.github.com/user"
oauth_code = None
class OAuthHandler(http.server.SimpleHTTPRequestHandler):
"""Handles the /callback request and extracts the code."""
def do_GET(self):
global oauth_code
parsed = urllib.parse.urlparse(self.path)
path = parsed.path
if path == "/callback":
query = urllib.parse.parse_qs(parsed.query)
oauth_code = query.get("code", [None])[0]
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"<h1>Authentication complete! You can close this tab.</h1>")
else:
self.send_error(404)
def start_server():
with socketserver.TCPServer(("localhost", 8000), OAuthHandler) as httpd:
print("Server running on http://localhost:8000 ...")
httpd.serve_forever()
def exchange_code_for_token(code):
response = requests.post(
TOKEN_URL,
headers={"Accept": "application/json"},
data={
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"code": code,
"redirect_uri": REDIRECT_URI
}
)
response.raise_for_status()
return response.json()["access_token"]
def get_user_profile(token):
response = requests.get(
USER_API_URL,
headers={"Authorization": f"Bearer {token}"}
)
response.raise_for_status()
return response.json()
def main():
# Start local server in background thread
server_thread = threading.Thread(target=start_server, daemon=True)
server_thread.start()
# Build the authorization URL
params = urllib.parse.urlencode({
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"scope": "read:user",
"response_type": "code"
})
auth_url = f"{AUTH_URL}?{params}"
print("Opening browser for authentication...")
webbrowser.open(auth_url)
# Wait for OAuth code
while oauth_code is None:
pass # simple wait loop
print(f"Authorization code received: {oauth_code}")
# Exchange code for token
token = exchange_code_for_token(oauth_code)
print(f"Access token received: {token}")
# Fetch user profile
profile = get_user_profile(token)
print("\n=== GitHub Profile ===")
print(profile)
if __name__ == "__main__":
main()
Assignment Tasks for Students#
1. Setup#
Create a GitHub OAuth app
Set redirect URL:
http://localhost:8000/callbackExport environment variables:
export CLIENT_ID=your_id
export CLIENT_SECRET=your_secret
2. Implement OAuth2 Flow#
Students must:
โ Start a local server
โ Listen for /callback
โ Parse query parameters
โ Open browser to GitHub OAuth
โ Capture authorization code
โ Send POST to https://github.com/login/oauth/access_token
โ Store & print the access token
โ Call /user with token
โ Display profile data
3. Written Questions#
Why is the redirect URI necessary?
Why is the authorization code exchanged for an access token?
Why is a local HTTP server needed?
What security risks exist when storing client secrets locally?
Explain the purpose of scopes.