Python Github Oauth Authentication Flow

Written by

in

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 requests library for HTTP calls: pip install requests.
  • Optional: python‑dotenv for managing .env files 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:

  1. Application name – e.g., “My Python Dashboard”.
  2. Homepage URL – the public URL of your app (or http://localhost:5000 for local testing).
  3. 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., repo for private repo access, user for 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}}

"
"

Logout

", 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

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *