Python Github Oauth Authentication Flow

Written by

in

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

  1. Register your application on GitHub. Obtain a Client ID and Client Secret.
  2. Redirect the user to GitHub’s authorization endpoint. Include scopes and a state parameter.
  3. GitHub authenticates the user and asks for consent.
  4. GitHub redirects back to your callback URL with an authorization code and the original state.
  5. Exchange the code for an access token. Make a POST request to GitHub’s token endpoint.
  6. 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:5000 for 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 state token to mitigate CSRF attacks.
  • Redirects the user to GitHub’s /login/oauth/authorize endpoint.
  • Validates the returned state and exchanges the code for 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 Secret on 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 state parameter. It protects against CSRF and should be stored per‑session.
  • <

Comments

Leave a Reply

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