When you want to let users log in to your Python web application using their GitHub accounts, the GitHub OAuth authentication flow is the industry‑standard solution. It enables you to securely obtain an access token, fetch user data, and personalize the experience without ever handling passwords directly. In this guide we’ll walk through every step of the Python GitHub OAuth authentication flow, from registering your app on GitHub to writing clean, production‑ready code with Flask (or any WSGI framework). By the end you’ll have a solid, SEO‑friendly reference that you can copy‑paste into your own projects.
Why Choose GitHub OAuth for Python Projects?
- Secure delegation: Users authenticate on GitHub, not on your server.
- Rich developer data: Access public profile, email, repositories, and more.
- Standardized protocol: OAuth 2.0 is widely supported and well‑documented.
- Python ecosystem: Libraries like
requests,authlib, andFlask‑Loginsimplify implementation.
Prerequisites Before You Start
- Python 3.8+ installed on your development machine.
- A GitHub account (obviously) and permission to create OAuth apps.
- Basic familiarity with a Python web framework (Flask, Django, FastAPI, etc.).
- Installed dependencies:
pip install Flask requests authlib
Step 1: Register Your OAuth Application on GitHub
Creating the app
- Navigate to GitHub Settings → Developer settings → OAuth Apps.
- Click New OAuth App.
- Fill in the required fields:
- Application name: e.g.,
MyPythonApp - Homepage URL:
https://myapp.example.com/ - Authorization callback URL:
https://myapp.example.com/auth/callback
- Application name: e.g.,
- Save the app. GitHub will generate a Client ID and a Client Secret**. Store them securely (environment variables are recommended).
Step 2: Understanding the OAuth 2.0 Flow
GitHub follows the standard Authorization Code Grant flow. The sequence is:
- Authorization request: Your app redirects the user to GitHub’s consent page.
- User grants permission: GitHub asks the user to approve the requested scopes.
- Redirect with code: GitHub redirects back to your
callback URLwith a temporarycodeparameter. - Token exchange: Your server sends the
code, client ID, and client secret to GitHub’s token endpoint. - Access token received: GitHub returns a JSON payload containing the
access_token. - API calls: Use the token in the
Authorization: token <access_token>header to fetch user data.
Step 3: Implementing the Flow in Python
Project structure
.
├─ app.py # Main Flask application
├─ config.py # Environment variables & settings
└─ requirements.txt # Dependencies
Configuration (config.py)
import os
class Config:
GITHUB_CLIENT_ID = os.getenv('GITHUB_CLIENT_ID')
GITHUB_CLIENT_SECRET = os.getenv('GITHUB_CLIENT_SECRET')
SECRET_KEY = os.getenv('FLASK_SECRET_KEY', 'dev-secret')
# The URL where GitHub will redirect after auth
GITHUB_REDIRECT_URI = os.getenv('GITHUB_REDIRECT_URI',
'http://localhost:5000/auth/callback')
# Scopes you need – adjust as required
GITHUB_OAUTH_SCOPE = 'read:user user:email'
Flask application (app.py)
from flask import Flask, redirect, url_for, request, session, jsonify
import requests
from authlib.integrations.flask_client import OAuth
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
# Initialize OAuth registry
oauth = OAuth(app)
github = oauth.register(
name='github',
client_id=app.config['GITHUB_CLIENT_ID'],
client_secret=app.config['GITHUB_CLIENT_SECRET'],
access_token_url='https://github.com/login/oauth/access_token',
authorize_url='https://github.com/login/oauth/authorize',
api_base_url='https://api.github.com/',
client_kwargs={'scope': app.config['GITHUB_OAUTH_SCOPE']},
)
@app.route('/')
def index():
user = session.get('github_user')
if user:
return f\"Welcome, {user['login']}!
\"
return \"Python GitHub OAuth Demo
Login with GitHub\"
@app.route('/login')
def login():
# Step 1 – redirect user to GitHub's consent page
redirect_uri = url_for('auth_callback', _external=True)
return github.authorize_redirect(redirect_uri)
@app.route('/auth/callback')
def auth_callback():
# Step 4 – exchange the temporary code for an access token
token = github.authorize_access_token()
# Store token securely (session is fine for demo)
session['github_token'] = token
# Step 5 – fetch user profile
resp = github.get('user')
profile = resp.json()
session['github_user'] = profile
# Optional: fetch primary email if not public
if not profile.get('email'):
emails_resp = github.get('user/emails')
emails = emails_resp.json()
primary = next((e['email'] for e in emails if e['primary']), None)
session['github_user']['email'] = primary
return redirect(url_for('index'))
@app.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)
Key points in the code
- Redirect URI is built dynamically with
url_for(..., _external=True)so it works locally and in production. github.authorize_redirect()automatically addsclient_id,redirect_uri, andscopeto the URL.github.authorize_access_token()handles the POST request to GitHub’s token endpoint and parses the JSON response.- All API calls use the same
githubclient, which injects theAuthorizationheader for you.
Step 4: Security Best Practices
- Never hard‑code client secrets. Use environment variables or a secret manager (AWS Secrets Manager, HashiCorp Vault, etc.).
- Use HTTPS for both your site and the callback URL. GitHub rejects insecure redirect URIs for production apps.
- Validate the
stateparameter if you implement it manually.authlibdoes this automatically, but custom implementations should generate a random nonce, store it in the session, and compare it on callback. - Limit scopes to the minimum required. Requesting
repowhen you only needread:userraises unnecessary risk. - Rotate client secrets periodically and revoke tokens when users log out or when suspicious activity is detected.
Common Pitfalls & How to Debug Them
- Redirect URI mismatch – GitHub will return a
redirect_uri mismatcherror if the URL you send does not exactly match the one registered. Double‑check trailing slashes and protocol (http vs https). - Missing
statevalidation – If you turn offauthlib’s built‑in state handling, CSRF attacks become possible. Always verify thestateyou sent. - Rate limiting – Unauthenticated GitHub API calls are limited to 60 requests/hour. Use the access token for higher limits (5,000 requests/hour per user).
- Expired authorization code – The temporary
codeis valid for only a few minutes. Ensure you exchange it immediately in the callback route. - Incorrect token parsing – GitHub returns the token as
application/x-www-form-urlencodedby default. Libraries likerequestshandle it, but if you parse manually, useresponse.json()orurllib.parse.parse_qs.
Testing the Flow Locally
During development you can use http://localhost:5000/auth/callback as the redirect URI. GitHub allows localhost URLs, but remember to switch to a fully qualified domain with HTTPS before going live. Tools like ngrok can expose your local server to the internet for quick integration tests.
Deploying to Production
- Set
FLASK_ENV=productionand configure a WSGI server (Gunicorn, uWSGI).
Leave a Reply