Python Github Oauth Authentication Flow

Written by

in

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, and Flask‑Login simplify 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

  1. Navigate to GitHub Settings → Developer settings → OAuth Apps.
  2. Click New OAuth App.
  3. 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
  4. 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:

  1. Authorization request: Your app redirects the user to GitHub’s consent page.
  2. User grants permission: GitHub asks the user to approve the requested scopes.
  3. Redirect with code: GitHub redirects back to your callback URL with a temporary code parameter.
  4. Token exchange: Your server sends the code, client ID, and client secret to GitHub’s token endpoint.
  5. Access token received: GitHub returns a JSON payload containing the access_token.
  6. 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']}!

Logout

\" 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 adds client_id, redirect_uri, and scope to 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 github client, which injects the Authorization header 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 state parameter if you implement it manually. authlib does 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 repo when you only need read:user raises 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

  1. Redirect URI mismatch – GitHub will return a redirect_uri mismatch error if the URL you send does not exactly match the one registered. Double‑check trailing slashes and protocol (http vs https).
  2. Missing state validation – If you turn off authlib’s built‑in state handling, CSRF attacks become possible. Always verify the state you sent.
  3. 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).
  4. Expired authorization code – The temporary code is valid for only a few minutes. Ensure you exchange it immediately in the callback route.
  5. Incorrect token parsing – GitHub returns the token as application/x-www-form-urlencoded by default. Libraries like requests handle it, but if you parse manually, use response.json() or urllib.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=production and configure a WSGI server (Gunicorn, uWSGI).

Comments

Leave a Reply

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