FULL SOLUTION β OAuth2 in Pure Python (GitHub Authorization Code Flow)#
No Flask, no FastAPI, only built-in modules + requests.
1. Solution Code#
Save as solution_oauth2_pure_python.py
"""
OAuth2 Authorization Code Flow (Pure Python Version)
----------------------------------------------------
This script demonstrates OAuth2 login with GitHub using only:
- http.server
- socketserver
- urllib.parse
- webbrowser
- requests
No web frameworks (Flask/FastAPI) are used.
"""
import http.server
import socketserver
import urllib.parse
import webbrowser
import threading
import requests
import os
import sys
# -------------------------------
# Configuration
# -------------------------------
CLIENT_ID = os.getenv("CLIENT_ID") or "REPLACE_ME"
CLIENT_SECRET = os.getenv("CLIENT_SECRET") or "REPLACE_ME"
if CLIENT_ID == "REPLACE_ME":
print("Error: Please set CLIENT_ID and CLIENT_SECRET environment variables.")
sys.exit(1)
AUTH_URL = "https://github.com/login/oauth/authorize"
TOKEN_URL = "https://github.com/login/oauth/access_token"
USER_URL = "https://api.github.com/user"
REDIRECT_URI = "http://localhost:8000/callback"
oauth_code = None # will store the authorization code from callback
# -------------------------------
# Step 1 β Local HTTP Callback Server
# -------------------------------
class OAuthCallbackHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
global oauth_code
parsed = urllib.parse.urlparse(self.path)
if parsed.path == "/callback":
params = urllib.parse.parse_qs(parsed.query)
oauth_code = params.get("code", [None])[0]
# Show confirmation page
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.end_headers()
self.wfile.write(b"<h1>Login Successful!</h1>You can close this tab.")
else:
self.send_error(404)
def start_callback_server():
with socketserver.TCPServer(("localhost", 8000), OAuthCallbackHandler) as httpd:
print("Callback server running on http://localhost:8000 ...")
httpd.serve_forever()
# -------------------------------
# Step 2 β Exchange Code for Token
# -------------------------------
def exchange_code_for_token(code: str) -> str:
"""Send POST request to GitHub to trade code for access token"""
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()
json_data = response.json()
return json_data["access_token"]
# -------------------------------
# Step 3 β Fetch Protected Resource
# -------------------------------
def get_github_profile(token: str) -> dict:
"""Call the GitHub API with the provided access token"""
response = requests.get(
USER_URL,
headers={"Authorization": f"Bearer {token}"}
)
response.raise_for_status()
return response.json()
# -------------------------------
# Main Program
# -------------------------------
def main():
global oauth_code
# Start callback server in background thread
server_thread = threading.Thread(target=start_callback_server, daemon=True)
server_thread.start()
# Construct authorization URL
params = urllib.parse.urlencode({
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"scope": "read:user",
"response_type": "code",
})
url = f"{AUTH_URL}?{params}"
print("\nOpening your browser to authenticate with GitHub...")
webbrowser.open(url)
# Wait for user authorization
print("Waiting for authorization...")
while oauth_code is None:
pass
print(f"\nAuthorization code received: {oauth_code}")
# Step 2: Exchange for access token
token = exchange_code_for_token(oauth_code)
print(f"Access token received: {token}")
# Step 3: Call API
profile = get_github_profile(token)
print("\n=== GitHub User Profile ===")
for k, v in profile.items():
print(f"{k}: {v}")
print("\nDone.")
if __name__ == "__main__":
main()
2. Explanation of the Solution#
Step 1 β Start Local HTTP Server#
We use http.server + socketserver to listen for GitHubβs redirect:
http://localhost:8000/callback?code=xxxx
The handler extracts the code and shows a simple HTML message.
Step 2 β Redirect User to GitHub#
Python opens the browser automatically:
webbrowser.open(auth_url)
Step 4 β Exchange Code for an Access Token#
requests.post(TOKEN_URL, ...)
GitHub returns:
{
"access_token": "xxxx",
"token_type": "bearer",
...
}
Step 5 β Use Token to Get Profile Information#
requests.get(USER_URL, headers={"Authorization": f"Bearer {token}"})
This returns userβs login, name, avatar URL, etc.
3. Solutions to Written Questions#
1. Why is the redirect URI necessary?#
It tells the OAuth provider (GitHub) where to send the authorization code after the user approves access.
3. Why do we need a local HTTP server?#
OAuth providers must redirect the user to a URL. Without a local server, your script cannot receive:
/callback?code=xxxx
4. What security risks exist with storing client secrets locally?#
Secrets can be read by any user on the system
Checked into Git repos by mistake
Malware can steal them
They cannot be revoked per-machine easily
5. Purpose of OAuth scopes?#
Scopes limit what the access token can do.
Example: read:user allows reading profile info, but not repositories.