Are you looking to let users log into your Python web app with their GitHub accounts? Leveraging GitHub’s OAuth 2.0 service not only streamlines the sign‑in experience but also opens the door to powerful GitHub APIs, enabling you to fetch repositories, issues, and more on behalf of authenticated users. In this guide we’ll walk through the entire Python GitHub OAuth authentication flow, from registering your OAuth app on GitHub to handling callbacks, exchanging authorization codes for access tokens, and finally securing the user session. Whether you’re building a Flask micro‑service or a full‑featured Django portal, the steps below will give you a solid, production‑ready foundation.
Why Choose GitHub OAuth for Your Python Application?
- Trusted Identity Provider: Millions of developers already have GitHub accounts, making sign‑in frictionless.
- Rich API Access: Once authenticated, you can call GitHub’s REST or GraphQL APIs to read user data, repositories, pull requests, and more.
- Secure Token Management: OAuth 2.0 uses short‑lived access tokens and optional refresh tokens, reducing the risk of credential leakage.
- Easy Integration: Popular Python frameworks (Flask, Django, FastAPI) have mature libraries that abstract most of the boilerplate.
Step‑by‑Step Overview of the GitHub OAuth Flow
- Register your application on GitHub. Obtain a
Client IDandClient Secret. - Redirect the user to GitHub’s authorization endpoint. Include scopes and a state parameter.
- GitHub authenticates the user and asks for consent.
- GitHub redirects back to your callback URL with an authorization
codeand the originalstate. - Exchange the code for an access token. Make a POST request to GitHub’s token endpoint.
- Use the access token to call GitHub APIs or store it securely for later use.
Registering Your OAuth App on GitHub
Before any code is written, you must create an OAuth app in your GitHub account:
- Navigate to GitHub Settings → Developer settings → OAuth Apps.
- Click “New OAuth App”.
- Fill in the required fields:
- Application name – e.g., “MyPythonApp”.
- Homepage URL – your public site or
http://localhost:5000for local testing. - Authorization callback URL – where GitHub will redirect after login (e.g.,
http://localhost:5000/auth/callback).
- Save to receive a Client ID and Client Secret. Keep the secret private!
Implementing the Flow in Flask
Flask is lightweight and perfect for demonstrating the OAuth dance. Below is a minimal, yet production‑ready, implementation.
Project Structure
myapp/
│
├─ app.py
├─ config.py
└─ requirements.txt
Installing Dependencies
pip install Flask requests python-dotenv
Configuration (config.py)
import os
from dotenv import load_dotenv
load_dotenv() # Loads variables from .env file
GITHUB_CLIENT_ID = os.getenv('GITHUB_CLIENT_ID')
GITHUB_CLIENT_SECRET = os.getenv('GITHUB_CLIENT_SECRET')
BASE_URL = os.getenv('BASE_URL', 'http://localhost:5000')
Core Application (app.py)
from flask import Flask, redirect, request, session, url_for, jsonify
import requests
from config import GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, BASE_URL
app = Flask(__name__)
app.secret_key = 'replace‑with‑a‑strong‑random‑value' # Needed for session handling
# ----------------------------------------------------------------------
# Helper: Build the GitHub authorization URL
# ----------------------------------------------------------------------
def github_authorize_url(state):
params = {
'client_id': GITHUB_CLIENT_ID,
'redirect_uri': f'{BASE_URL}{url_for("callback")}',
'scope': 'read:user repo', # Adjust scopes as needed
'state': state
}
url = 'https://github.com/login/oauth/authorize'
request_url = requests.Request('GET', url, params=params).prepare().url
return request_url
# ----------------------------------------------------------------------
# Route: Home – offers a “Login with GitHub” button
# ----------------------------------------------------------------------
@app.route('/')
def index():
return (
f'Welcome to MyPythonApp
'
f'Login with GitHub'
)
# ----------------------------------------------------------------------
# Route: Initiates the OAuth flow
# ----------------------------------------------------------------------
@app.route('/login')
def login():
# Generate a random state token to protect against CSRF
state = os.urandom(16).hex()
session['oauth_state'] = state
return redirect(github_authorize_url(state))
# ----------------------------------------------------------------------
# Route: Callback – GitHub redirects here after user consent
# ----------------------------------------------------------------------
@app.route('/auth/callback')
def callback():
# Verify state parameter
received_state = request.args.get('state')
if received_state != session.get('oauth_state'):
return 'Invalid state parameter', 400
code = request.args.get('code')
token_url = 'https://github.com/login/oauth/access_token'
headers = {'Accept': 'application/json'}
payload = {
'client_id': GITHUB_CLIENT_ID,
'client_secret': GITHUB_CLIENT_SECRET,
'code': code,
'redirect_uri': f'{BASE_URL}{url_for(\"callback\")}',
'state': received_state
}
# Exchange code for access token
token_response = requests.post(token_url, data=payload, headers=headers)
token_json = token_response.json()
access_token = token_json.get('access_token')
if not access_token:
return f'Error retrieving access token: {token_json}', 400
# Store token in session (or a database for longer‑term storage)
session['github_token'] = access_token
return redirect(url_for('profile'))
# ----------------------------------------------------------------------
# Route: Profile – demonstrates an authenticated API call
# ----------------------------------------------------------------------
@app.route('/profile')
def profile():
token = session.get('github_token')
if not token:
return redirect(url_for('index'))
api_url = 'https://api.github.com/user'
headers = {'Authorization': f'token {token}'}
user_resp = requests.get(api_url, headers=headers).json()
# Simple JSON output; in a real app you’d render a template
return jsonify(user_resp)
if __name__ == '__main__':
app.run(debug=True)
This script performs the full OAuth cycle:
- Generates a
statetoken to mitigate CSRF attacks. - Redirects the user to GitHub’s
/login/oauth/authorizeendpoint. - Validates the returned
stateand exchanges thecodefor an access token. - Uses the token to request the authenticated user’s profile via
https://api.github.com/user.
Adapting the Flow for Django
If you prefer Django, the same concepts apply, but you’ll typically rely on a library such as social-auth-app-django. Below is a concise configuration outline.
Install Required Packages
pip install Django social-auth-app-django python-dotenv
Update settings.py
import os
from dotenv import load_dotenv
load_dotenv()
AUTHENTICATION_BACKENDS = (
'social_core.backends.github.GithubOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
SOCIAL_AUTH_GITHUB_KEY = os.getenv('GITHUB_CLIENT_ID')
SOCIAL_AUTH_GITHUB_SECRET = os.getenv('GITHUB_CLIENT_SECRET')
SOCIAL_AUTH_GITHUB_SCOPE = ['read:user', 'repo']
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'
URL Configuration (urls.py)
from django.urls import path, include
from django.contrib import admin
urlpatterns = [
path('admin/', admin.site.urls),
path('auth/', include('social_django.urls', namespace='social')),
path('', include('myapp.urls')), # Your app’s own routes
]
Template Snippet for Login
<a href="{% url 'social:begin' 'github' %}">Login with GitHub</a>
With social-auth-app-django handling the heavy lifting, you get automatic state verification, token storage, and user model integration out of the box.
Best Practices for Secure Token Management
- Never expose
Client Secreton the client side. Keep it on the server and use environment variables. - Use HTTPS in production. OAuth redirects over plain HTTP are vulnerable to man‑in‑the‑middle attacks.
- Validate the
stateparameter. It protects against CSRF and should be stored per‑session.
<
Leave a Reply