When you build a Python app that talks to GitHub—whether it’s a personal dashboard, a CI/CD tool, or a community‑driven bot—securely identifying users is a must. The most reliable way to do that is by leveraging GitHub’s OAuth 2.0 implementation. In this guide we’ll walk through the complete Python GitHub OAuth authentication flow, from registering your app on GitHub to exchanging the authorization code for an access token and finally calling the GitHub API. You’ll also get ready‑to‑run code snippets for Flask and Django, best‑practice security tips, and troubleshooting tricks that keep your integration smooth and SEO‑friendly.
Understanding OAuth 2.0 and GitHub
What is OAuth 2.0?
OAuth 2.0 is an open standard for delegated authorization. Instead of handling passwords, a client application redirects the user to an authorization server (GitHub) where the user grants limited permissions—called scopes—to the client. After consent, the server redirects back with an authorization code, which the client exchanges for an access token. The token is then used in API calls, keeping the user’s credentials out of your codebase.
Why GitHub uses OAuth
GitHub’s API protects private repositories, organization data, and user actions. By using OAuth, GitHub ensures that third‑party apps only receive the permissions the user explicitly approves. This model also lets you revoke access at any time from the GitHub settings page, providing an extra layer of safety for both developers and end users.
Prerequisites for a Python Implementation
- Python 3.8+ installed on your development machine.
- A GitHub account to register the OAuth application.
- Either Flask or Django installed (the examples use Flask, but the flow is identical for Django).
- The
requestslibrary for HTTP calls:pip install requests. - Optional:
python‑dotenvfor managing.envfiles securely.
Step‑by‑Step Python GitHub OAuth Flow
1. Register Your Application on GitHub
Navigate to GitHub Settings → Developer settings → OAuth Apps → New OAuth App and fill in:
- Application name – e.g., “My Python Dashboard”.
- Homepage URL – the public URL of your app (or
http://localhost:5000for local testing). - Authorization callback URL – the endpoint that will receive the code, such as
http://localhost:5000/callback.
After saving, GitHub provides a Client ID and a Client Secret**. Store these securely; never commit them to source control.
2. Build the Authorization URL
The first user‑facing step is to redirect the user to GitHub’s authorization endpoint. The URL must include:
https://github.com/login/oauth/authorize?
client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_CALLBACK_URL
&scope=repo,user
&state=RANDOM_STRING
Key parameters:
- client_id – your app’s public identifier.
- redirect_uri – must match the URL registered with GitHub.
- scope – a space‑separated list of permissions (e.g.,
repofor private repo access,userfor profile data). - state – a cryptographically random string to prevent CSRF attacks; store it in the user’s session and verify it on callback.
3. Handle the Callback and Exchange Code for a Token
When the user authorizes, GitHub redirects to your /callback endpoint with code and state query parameters. Verify the state value, then POST the following to https://github.com/login/oauth/access_token:
POST https://github.com/login/oauth/access_token
Headers:
Accept: application/json
Body (application/x-www-form-urlencoded):
client_id=YOUR_CLIENT_ID
client_secret=YOUR_CLIENT_SECRET
code=AUTHORIZATION_CODE
redirect_uri=YOUR_CALLBACK_URL
state=STATE_VALUE
The response contains a JSON object with access_token, token_type (always “bearer”), and the granted scope. Store the token securely—preferably in a server‑side session or a signed JWT.
4. Use the Access Token to Call the GitHub API
All subsequent GitHub API requests include the token in the Authorization header:
GET https://api.github.com/user
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/vnd.github.v3+json
Typical use cases include fetching the authenticated user’s profile, listing repositories, or creating issues. Remember to respect rate limits (5000 requests per hour per token) and handle 403 responses gracefully.
5. Refresh Tokens and Revoking Access (GitHub specifics)
GitHub does not issue refresh tokens; access tokens are long‑lived until the user revokes them. To programmatically revoke a token, send a DELETE request to https://api.github.com/applications/:client_id/token with basic auth using the Client ID and Client Secret. This is useful for “Logout” functionality.
Sample Code with Flask
import os
import secrets
from urllib.parse import urlencode
import requests
from flask import Flask, redirect, request, session, url_for, render_template_string
from dotenv import load_dotenv
load_dotenv() # Loads CLIENT_ID, CLIENT_SECRET from .env
app = Flask(__name__)
app.secret_key = os.urandom(24) # Needed for session handling
GITHUB_AUTH_URL = "https://github.com/login/oauth/authorize"
GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token"
GITHUB_API_URL = "https://api.github.com"
@app.route("/")
def index():
user = session.get("github_user")
if user:
return render_template_string(
"
Logged in as {{login}}
"
"
", login=user["login"]
)
return render_template_string(
"
Welcome! Login with GitHub
"
)
@app.route("/login")
def login():
state = secrets.token_urlsafe(16)
session["oauth_state"] = state
params = {
"client_id": os.getenv("CLIENT_ID"),
"redirect_uri": url_for("callback", _external=True),
"scope": "repo user",
"state": state,
}
return redirect(f"{GITHUB_AUTH_URL}?{urlencode(params)}")
@app.route("/callback")
def callback():
# Verify state to mitigate CSRF
if request.args.get("state") != session.get("oauth_state"):
return "State mismatch", 400
code = request.args.get("code")
token_resp = requests.post(
GITHUB_TOKEN_URL,
headers={"Accept": "application/json"},
data={
"client_id": os.getenv("CLIENT_ID"),
"client_secret": os.getenv("CLIENT_SECRET"),
"code": code,
"redirect_uri": url_for("callback", _external=True),
"state": session["oauth_state"],
},
)
token_resp.raise_for_status()
access_token = token_resp.json()["access_token"]
# Fetch user profile
user_resp = requests.get(
f"{GITHUB_API_URL}/user",
headers={"Authorization": f"Bearer {access_token}", "Accept": "application/vnd.github.v3+json"},
)
user_resp
Leave a Reply