Python Github Oauth Authentication Flow

Written by

in

When you want to let users log in to your Python web app with their GitHub accounts, the OAuth 2.0 authentication flow is the industry‑standard solution. In this guide we walk through every step of the Python GitHub OAuth authentication flow, from registering your app on GitHub to handling callbacks, exchanging tokens, and securely calling the GitHub API. Whether you are building a Flask micro‑service, a Django portal, or a simple script, the concepts and code snippets below will give you a solid, SEO‑friendly foundation.

Why Choose GitHub OAuth for Python Projects?

  • Trusted identity provider: Millions of developers already have GitHub accounts.
  • Fine‑grained scopes: Request only the permissions you need (e.g., read:user, repo).
  • Easy integration: GitHub follows the standard OAuth 2.0 protocol, so the same flow works with Flask, Django, FastAPI, or plain requests.
  • Robust documentation: GitHub’s developer docs provide clear examples and a sandbox for testing.

Prerequisites Before You Start

  • Python 3.8+ installed on your development machine.
  • A GitHub account (free or paid) to create an OAuth App.
  • Basic knowledge of HTTP, redirects, and JSON.
  • One of the following web frameworks installed:
    • Flask (pip install Flask)
    • Django (pip install Django)

Step 1: Register Your Application on GitHub

GitHub needs to know which application is requesting access. Follow these steps to create an OAuth App:

  1. Log in to GitHub and navigate to Settings → Developer settings → OAuth Apps.
  2. Click New OAuth App and fill in the form:
    • Application name: e.g., MyPythonOAuthDemo
    • Homepage URL: the public URL of your app (e.g., https://example.com)
    • Authorization callback URL: the endpoint that will receive the code, such as https://example.com/auth/callback
  3. After saving, GitHub will provide a Client ID and a Client Secret. Store them securely—never commit the secret to version control.

Step 2: Understanding the OAuth 2.0 Flow

The flow consists of four core steps. Visualizing them helps you debug problems quickly.

1️⃣ User clicks “Login with GitHub” → 
2️⃣ Your app redirects to https://github.com/login/oauth/authorize (Authorization Request) → 
3️⃣ GitHub authenticates the user and redirects back to your callback URL with a temporary code → 
4️⃣ Your server exchanges the code for an access_token → 
5️⃣ Use the access_token to call GitHub APIs (e.g., fetch user profile)

2️⃣ Authorization Request

Build a URL that includes your client_id, requested scope, and a random state parameter to protect against CSRF attacks.

https://github.com/login/oauth/authorize?
    client_id=YOUR_CLIENT_ID
    &redirect_uri=https%3A%2F%2Fexample.com%2Fauth%2Fcallback
    &scope=read:user%20user:email
    &state=RANDOM_STRING

3️⃣ Callback & Code Exchange

GitHub redirects the user to your callback URL with two query parameters: code and state. Verify the state matches the one you generated, then POST the code to GitHub’s token endpoint.

POST https://github.com/login/oauth/access_token
Headers: Accept: application/json
Body:
    client_id=YOUR_CLIENT_ID
    &client_secret=YOUR_CLIENT_SECRET
    &code=RECEIVED_CODE
    &redirect_uri=YOUR_CALLBACK_URL
    &state=ORIGINAL_STATE

The response contains an access_token (and optionally a refresh_token for GitHub Enterprise). Store this token in a secure session or database.

4️⃣ Making Authenticated API Calls

All subsequent GitHub API requests include the token in an Authorization: token <access_token> header.

GET https://api.github.com/user
Headers:
    Authorization: token OAUTH_ACCESS_TOKEN
    Accept: application/vnd.github.v3+json

Step 3: Implementing the Flow in Flask

The following minimal Flask app demonstrates the complete OAuth cycle. Replace the placeholder values with your real credentials.

import os
import secrets
from urllib.parse import urlencode

import requests
from flask import Flask, redirect, request, session, url_for, render_template_string

app = Flask(__name__)
app.secret_key = os.getenv('FLASK_SECRET_KEY', secrets.token_hex(16))

# ---- Configuration ----
CLIENT_ID = os.getenv('GITHUB_CLIENT_ID')
CLIENT_SECRET = os.getenv('GITHUB_CLIENT_SECRET')
AUTH_BASE_URL = 'https://github.com/login/oauth/authorize'
TOKEN_URL = 'https://github.com/login/oauth/access_token'
API_BASE_URL = 'https://api.github.com'

# ---- Helper ----
def github_auth_url(state):
    params = {
        'client_id': CLIENT_ID,
        'redirect_uri': url_for('callback', _external=True),
        'scope': 'read:user user:email',
        'state': state,
    }
    return f"{AUTH_BASE_URL}?{urlencode(params)}"

# ---- Routes ----
@app.route('/')
def index():
    user = session.get('github_user')
    if user:
        return render_template_string(
            '

Logged in as {{ user["login"] }}

' '

Logout

', user=user, ) return 'Login with GitHub' @app.route('/login') def login(): state = secrets.token_urlsafe(16) session['oauth_state'] = state return redirect(github_auth_url(state)) @app.route('/auth/callback') def callback(): # Verify state to prevent CSRF state = request.args.get('state') if not state or state != session.pop('oauth_state', None): return 'Invalid state parameter', 400 code = request.args.get('code') if not code: return 'Missing code parameter', 400 # Exchange code for access token token_resp = requests.post( TOKEN_URL, headers={'Accept': 'application/json'}, data={ 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, 'code': code, 'redirect_uri': url_for('callback', _external=True), 'state': state, }, ) token_json = token_resp.json() access_token = token_json.get('access_token') if not access_token: return f"Error retrieving token: {token_json}", 400 # Fetch user profile user_resp = requests.get( f"{API_BASE_URL}/user", headers={'Authorization': f'token {access_token}'}, ) session['github_user'] = user_resp.json() 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 highlighted in the code:

  • State validation: Prevents CSRF attacks.
  • Secure session handling via Flask’s secret key.
  • Use of Accept: application/json to receive a JSON token response.
  • Storing only the minimal user data needed for the session.

Step 4: Implementing the Flow in Django (Optional)

If you prefer Django, the same steps apply. Below is a concise view‑function example that you can drop into a views.py file.

import os, secrets, requests
from urllib.parse import urlencode
from django.shortcuts import redirect, render
from django.http import HttpResponseBadRequest
from django.conf import settings

CLIENT_ID = os.getenv('GITHUB_CLIENT_ID')
CLIENT_SECRET = os.getenv('GITHUB_CLIENT_SECRET')
REDIRECT_URI = 'https://yourdomain.com/github/callback/'

def github_login(request):
state = secrets.token_urlsafe(16)
request.session['oauth_state'] = state
params = {
'client_id': CLIENT_ID,
'redirect_uri': REDIRECT_URI,
'scope': 'read:user user:email',
'state': state,
}
return redirect('https://github.com/login/oauth/authorize?' + urlencode(params))

def github_callback(request):
state = request.GET.get('state')
if not state or state != request.session.pop('oauth_state', None):
return HttpResponseBadRequest('Invalid state')
code = request.GET.get('code')
token_resp =

Comments

Leave a Reply

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