Python Github Oauth Authentication Flow

Written by

in

When developers want to let users log in to their Python web applications with a GitHub account, the GitHub OAuth authentication flow is the most reliable and secure choice. Not only does it off‑load password management to GitHub, but it also grants your app access to the user’s public profile and repositories (with permission). In this guide you’ll learn every step of the Python GitHub OAuth authentication flow, from creating a GitHub OAuth app to handling access tokens safely in Flask or Django. By the end, you’ll be able to copy‑paste a ready‑to‑run code snippet, understand the underlying OAuth2 protocol, and avoid the most common pitfalls that trip up beginners.

Why Use GitHub OAuth with Python?

  • Security first: Users never share passwords with your app.
  • Developer‑friendly: GitHub’s API is well‑documented and widely used in CI/CD pipelines.
  • Scalable: OAuth tokens can be refreshed or revoked without touching your database.
  • SEO boost: Articles that target “Python GitHub OAuth authentication flow” rank well because developers frequently search for this exact phrase.

OAuth2 Basics You Should Know

OAuth2 is an authorization framework that separates the authentication step (who you are) from the resource‑access step (what you can do). The typical flow includes four key stages:

  1. Authorization Request: Your app redirects the user to GitHub’s https://github.com/login/oauth/authorize endpoint.
  2. User Consent: The user logs in (if needed) and authorizes your requested scopes.
  3. Authorization Code: GitHub redirects back to your redirect_uri with a temporary code query parameter.
  4. Access Token Exchange: Your server exchanges the code for an access_token via a POST request to https://github.com/login/oauth/access_token.

Once you have the access_token, you can call GitHub’s API on behalf of the user.

Step 1 – Create a GitHub OAuth App

Before any Python code runs, you need a registered OAuth application on GitHub:

  • Log in to GitHub and go to Settings → Developer settings → OAuth Apps.
  • Click “New OAuth App”.
  • Fill in:
    • Application name – e.g., “MyPythonApp”.
    • Homepage URL – your site’s root (e.g., https://example.com).
    • Authorization callback URL – the endpoint that will receive the code. For Flask, https://example.com/callback works well.
  • After creation, note the Client ID and Client Secret. You’ll reference these in your Python code.

Step 2 – Choose a Python Web Framework

Both Flask and Django have excellent support for OAuth. This tutorial focuses on Flask for brevity, but the concepts translate directly to Django or FastAPI.

Step 3 – Install Required Packages

pip install Flask requests python-dotenv

python-dotenv keeps your CLIENT_ID and CLIENT_SECRET out of source control.

Step 4 – Store Secrets Securely

Create a .env file in your project root:

# .env
GITHUB_CLIENT_ID=your_client_id_here
GITHUB_CLIENT_SECRET=your_client_secret_here
SECRET_KEY=super_secret_flask_key

Never commit this file to a public repository.

Step 5 – Implement the OAuth Flow in Flask

5.1 Basic Flask App Skeleton

from flask import Flask, redirect, request, session, url_for, render_template_string
import os, requests
from dotenv import load_dotenv

load_dotenv()
app = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY')

5.2 Build the Authorization URL

@app.route('/')
def index():
    return render_template_string('''
        <h2>Login with GitHub</h2>
        <a href="{{ url_for('login') }}">Sign in</a>
    ''')

5.3 Redirect to GitHub

@app.route('/login')
def login():
    client_id = os.getenv('GITHUB_CLIENT_ID')
    redirect_uri = url_for('callback', _external=True)
    scope = 'read:user repo'  # Adjust scopes as needed
    auth_url = (
        f"https://github.com/login/oauth/authorize?"
        f"client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}"
    )
    return redirect(auth_url)

5.4 Handle the Callback and Exchange Code for Token

@app.route('/callback')
def callback():
    code = request.args.get('code')
    if not code:
        return 'Error: No code provided', 400

    token_url = 'https://github.com/login/oauth/access_token'
    headers = {'Accept': 'application/json'}
    payload = {
        'client_id': os.getenv('GITHUB_CLIENT_ID'),
        'client_secret': os.getenv('GITHUB_CLIENT_SECRET'),
        'code': code,
        'redirect_uri': url_for('callback', _external=True)
    }

    token_response = requests.post(token_url, headers=headers, data=payload)
    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 securely in the session (or a DB for production)
    session['github_token'] = access_token
    return redirect(url_for('profile'))

5.5 Fetch the User Profile Using the Token

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

    user_api = 'https://api.github.com/user'
    headers = {'Authorization': f'token {token}'}
    resp = requests.get(user_api, headers=headers)
    if resp.status_code != 200:
        return f'Failed to fetch profile: {resp.text}', 400

    user_data = resp.json()
    return render_template_string('''
        <h2>GitHub Profile</h2>
        <img src="{{ avatar_url }}" width="100">
        <p><strong>Name:</strong> {{ name }}</p>
        <p><strong>Username:</strong> {{ login }}</p>
        <p><strong>Public Repos:</strong> {{ public_repos }}</p>
        <a href="{{ url_for('logout') }}">Logout</a>
    ''', **user_data)

5.6 Logout Endpoint

@app.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('index'))

Step 6 – Run the Application

if __name__ == '__main__':
    app.run(debug=True)

Visit http://127.0.0.1:5000/, click “Sign in”, and you’ll be redirected through the full Python GitHub OAuth authentication flow. After granting permission, you’ll see your GitHub avatar and basic profile data.

Best Practices for Token Management

  • Never expose the token in URLs or client‑side JavaScript. Keep it on the server or in an HttpOnly cookie.
  • Use short‑lived tokens when possible. GitHub’s tokens don’t expire by default, but you can revoke them via the user’s settings or the API.
  • Store tokens encrypted at rest. If you persist them in a database, encrypt with a key management service (KMS).
  • Validate scopes. Request only the permissions you need; excess scopes raise security concerns and reduce conversion rates.

Common Pitfalls and How to Debug Them

Redirect URI Mismatch

If GitHub returns error=redirect_uri_mismatch, double‑check that the Authorization callback URL in your GitHub app exactly matches the redirect_uri you send in the /login route. Include the trailing slash if you used one.

State Parameter Omission

While the simple example above omits the state parameter for brevity, production apps should generate a random state token, store it in the session, and verify it on callback to prevent CSRF attacks.

Comments

Leave a Reply

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