Python Github Oauth Authentication Flow

Written by

in

Python GitHub OAuth authentication flow is the modern way to let users log in to your web app using their GitHub credentials, while keeping passwords safely out of your hands. In this guide we’ll walk through the theory behind OAuth 2.0, the exact steps to set up a GitHub‑based login in Python, and best‑practice tips that keep your integration secure and SEO‑friendly. Whether you’re building a Flask micro‑service or a full‑blown Django portal, the patterns described here will work out of the box.

Understanding OAuth 2.0 and GitHub’s Role

What is OAuth 2.0?

OAuth 2.0 is an open‑standard authorization framework that enables third‑party applications to obtain limited access to an HTTP service—such as GitHub—on behalf of a resource owner. The flow typically involves three parties:

  • Client – your Python web app.
  • Authorization Server – GitHub’s OAuth endpoint.
  • Resource Server – the GitHub API that serves user data.

The client never sees the user’s password. Instead, it receives a short‑lived access_token that can be used to call protected endpoints.

Why Choose GitHub for Authentication?

GitHub is a trusted identity provider for developers. By leveraging GitHub OAuth, you gain:

  • Instant credibility—users recognize the familiar GitHub login screen.
  • Rich profile data (username, email, public repos) without building a custom user database.
  • Fine‑grained permission scopes (e.g., read:user, repo) that let you request only what you need.

Prerequisites for a Python GitHub OAuth Flow

  • A GitHub account and access to GitHub Developer Settings.
  • Python 3.8+ installed locally.
  • One of the following web frameworks: Flask (lightweight) or Django (full‑stack).
  • Basic knowledge of HTTP, redirects, and JSON handling.

Step‑by‑Step Implementation with Flask

1. Register Your Application on GitHub

Navigate to Developer Settings → OAuth Apps → New OAuth App and fill in:

  • Application name – e.g., “MyPythonApp”.
  • Homepage URL – the public URL of your app.
  • Authorization callback URL – e.g., http://localhost:5000/callback for local testing.

After saving, GitHub provides a Client ID and Client Secret. Keep the secret out of source control!

2. Install Required Packages

pip install Flask requests

3. Create the Flask App and Configuration

from flask import Flask, redirect, request, session, url_for, jsonify
import requests
import os

app = Flask(__name__)
app.secret_key = os.urandom(24)  # Secure session cookies

# Load credentials from environment variables
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_USER_URL = 'https://api.github.com/user'

4. Build the Authorization URL

When a user clicks “Login with GitHub”, redirect them to GitHub’s consent page.

@app.route('/login')
def login():
    params = {
        'client_id': CLIENT_ID,
        'redirect_uri': url_for('callback', _external=True),
        'scope': 'read:user user:email',
        'state': os.urandom(8).hex()  # CSRF protection
    }
    session['oauth_state'] = params['state']
    auth_url = f"{AUTH_BASE_URL}?{requests.compat.urlencode(params)}"
    return redirect(auth_url)

5. Handle the Callback and Exchange Code for Token

GitHub redirects back with code and state. Verify the state, then request an access token.

@app.route('/callback')
def callback():
    # Verify state to prevent CSRF attacks
    if request.args.get('state') != session.get('oauth_state'):
        return 'Invalid state parameter', 400

    code = request.args.get('code')
    token_response = 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': session['oauth_state']
        }
    )
    token_json = token_response.json()
    access_token = token_json.get('access_token')
    if not access_token:
        return 'Failed to obtain access token', 400

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

6. Access the GitHub API with the Token

Now you can fetch the authenticated user’s profile.

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

    user_resp = requests.get(
        API_USER_URL,
        headers={'Authorization': f'token {token}',
                 'Accept': 'application/vnd.github.v3+json'}
    )
    user_data = user_resp.json()
    return jsonify({
        'login': user_data.get('login'),
        'name': user_data.get('name'),
        'avatar_url': user_data.get('avatar_url'),
        'public_repos': user_data.get('public_repos')
    })

Alternative: Using Django with social-auth-app-django

If you prefer Django, the social-auth-app-django package abstracts most of the flow. After installing:

pip install social-auth-app-django

add the following to settings.py:

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', 'user:email']

Include the URLs:

urlpatterns = [
    path('auth/', include('social_django.urls', namespace='social')),
    # … other routes …
]

Then a simple template link <a href="{% url 'social:begin' 'github' %}">Login with GitHub</a> launches the same OAuth flow without writing any request‑handling code yourself.

Common Pitfalls and How to Debug Them

  • Mismatched redirect URI – GitHub will reject the request if the redirect_uri does not exactly match the one registered.
  • Missing state verification – Skipping CSRF protection can expose your app to token‑theft attacks.
  • Using the wrong token endpoint – Always POST to https://github.com/login/oauth/access_token with an Accept: application/json header.
  • Expired or revoked tokens – Implement graceful handling (e.g., redirect to /login when a 401 is returned

Comments

Leave a Reply

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