Python Github Oauth Authentication Flow

Written by

in

When you build a Python app that needs to interact with a user’s GitHub account, the most secure and user‑friendly way to do it is through GitHub’s OAuth 2.0 authentication flow. In this guide we’ll walk through every step—from registering your OAuth app on GitHub to exchanging the authorization code for an access token and finally calling the GitHub API—all using clean, production‑ready Python code. Whether you’re using Flask, Django, or a simple script, mastering this flow will make your application feel native, protect user data, and boost your SEO by targeting the keyword‑rich phrase “Python GitHub OAuth authentication flow.”

Understanding OAuth 2.0 Basics

What is OAuth 2.0?

OAuth 2.0 is an open standard for delegated authorization. Instead of asking users for their password, an app redirects them to the service provider (GitHub) where they log in and grant specific permissions. The provider then returns a short‑lived access token that the app can use to act on the user’s behalf.

Why GitHub Uses OAuth

  • Security: Passwords never leave GitHub’s domain.
  • Granular scopes: You can request only the permissions you truly need (e.g., repo, read:user).
  • Revocable tokens: Users can revoke access at any time without changing their password.

Prerequisites for Python GitHub OAuth

  1. Python 3.8+ installed on your development machine.
  2. A GitHub account (obviously) and the ability to create an OAuth App in your profile settings.
  3. A web framework – the examples use Flask, but the same concepts apply to Django, FastAPI, or plain http.server.
  4. Familiarity with requests library for making HTTP calls.
  5. Environment variable handling (e.g., python‑dotenv) to keep client secrets out of source control.

Step‑by‑Step Implementation in Python

1. Register a GitHub OAuth App

Go to GitHub Settings → Developer settings → OAuth Apps and click “New OAuth App”. Fill in:

  • Application name – e.g., “My Python Dashboard”.
  • Homepage URL – your local development URL, such as http://localhost:5000.
  • Authorization callback URL – the endpoint that will receive the code, e.g., http://localhost:5000/callback.

After saving, GitHub will give you a Client ID and a Client Secret**. Store both securely; never commit the secret to Git.

2. Set Up a Flask Project

# app.py
from flask import Flask, redirect, request, session, url_for, jsonify
import os
import requests
from urllib.parse import urlencode

app = Flask(__name__)
app.secret_key = os.getenv('FLASK_SECRET_KEY', 'dev-secret')  # replace in production

# Load GitHub credentials from environment
GITHUB_CLIENT_ID = os.getenv('GITHUB_CLIENT_ID')
GITHUB_CLIENT_SECRET = os.getenv('GITHUB_CLIENT_SECRET')

3. Build the Authorization URL

The user is sent to GitHub’s authorize endpoint with a few query parameters. The most common are client_id, redirect_uri, scope, and a random state token to prevent CSRF attacks.

@app.route('/')
def index():
    state = os.urandom(16).hex()
    session['oauth_state'] = state
    params = {
        'client_id': GITHUB_CLIENT_ID,
        'redirect_uri': url_for('callback', _external=True),
        'scope': 'read:user repo',
        'state': state,
        'allow_signup': 'true'
    }
    auth_url = f"https://github.com/login/oauth/authorize?{urlencode(params)}"
    return f'<a href="{auth_url}">Login with GitHub</a>'

4. Handle the Callback and Exchange Code for a Token

GitHub redirects the user back to /callback with code and state. Verify the state, then POST to GitHub’s token endpoint.

@app.route('/callback')
def callback():
    # Verify state parameter
    received_state = request.args.get('state')
    if received_state != session.get('oauth_state'):
        return 'State mismatch. Potential CSRF attack.', 400

    code = request.args.get('code')
    token_url = 'https://github.com/login/oauth/access_token'
    headers = {'Accept': 'application/json'}
    data = {
        'client_id': GITHUB_CLIENT_ID,
        'client_secret': GITHUB_CLIENT_SECRET,
        'code': code,
        'redirect_uri': url_for('callback', _external=True),
        'state': received_state
    }
    token_response = requests.post(token_url, headers=headers, data=data)
    token_json = token_response.json()
    access_token = token_json.get('access_token')
    if not access_token:
        return f"Error retrieving token: {token_json}", 400

    session['github_token'] = access_token
    return redirect(url_for('profile'))

5. Access the GitHub API with the Token

Now you can make authenticated requests on behalf of the user. Below we fetch the user’s public profile and list their repositories.

@app.route('/profile')
def profile():
    token = session.get('github_token')
    if not token:
        return redirect(url_for('index'))

    api_headers = {'Authorization': f'token {token}'}
    user_resp = requests.get('https://api.github.com/user', headers=api_headers)
    repos_resp = requests.get('https://api.github.com/user/repos', headers=api_headers, params={'per_page': 5})

    user_data = user_resp.json()
    repos = repos_resp.json()

    return jsonify({
        'login': user_data.get('login'),
        'name': user_data.get('name'),
        'public_repos': user_data.get('public_repos'),
        'sample_repos': [repo['full_name'] for repo in repos]
    })

Common Pitfalls and Debugging Tips

  • Missing or mismatched state value: Always store the generated state in the session and compare it on callback.
  • Wrong redirect URI: The URL registered on GitHub must exactly match the one you send in the request (including trailing slashes).
  • Using the wrong token endpoint: GitHub expects a POST to https://github.com/login/oauth/access_token with an Accept: application/json header; otherwise you’ll receive a URL‑encoded response.
  • Scope errors: Requesting a scope you haven’t enabled in your OAuth app (or that the user denied) will result in a 403 when calling the API.
  • Token expiration: GitHub tokens are long‑lived but can be revoked. Implement graceful fallback (e.g., redirect to login) if an API call returns 401.

Enhancing Security and Best Practices

  • Store GITHUB_CLIENT_SECRET in environment variables or a secret manager; never hard‑code.
  • Use https in production. OAuth redirects over plain HTTP expose the code and can be intercepted.
  • Set a short session.permanent lifetime and rotate the state token on each auth attempt.
  • Limit scopes to the minimum required. For read‑only operations, use read:user instead of repo.
  • Validate the access_token by calling GET https://api.github.com/user before storing it in the session.

Testing the Flow Locally

Running the Flask app on

Comments

Leave a Reply

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