Python  

Implementing Discord Social Login in Python

Discord's OAuth2 integration allows developers to authenticate users through their Discord accounts, providing a seamless login experience while gaining access to user profile data. In this guide, we'll walk through implementing Discord social login in a Python web application using Flask.

Setting Up Your Discord Application

  1. Go to the Discord Developer Portal.
  2. Click "New Application" and give it a name.
  3. Navigate to the "OAuth2" section.
  4. Note your Client ID and generate a Client Secret.
  5. Add a redirect URI (e.g., http://127.0.0.1:5000/callback).

Install the required dependencies by creating a requirements.txt file.

discord.py==2.5.2
Flask==3.1.0
python-dotenv==1.0.1
requests==2.32.3
requests-oauthlib==2.0.0
flasgger==0.9.7.1

Configuration

Create a config.py file to manage your configuration.

import os
from dotenv import load_dotenv

load_dotenv()

class Config:
    CLIENT_ID = os.getenv('CLIENT_ID')
    CLIENT_SECRET = os.getenv('CLIENT_SECRET')
    REDIRECT_URI = os.getenv('DISCORD_REDIRECT_URI')
    AUTHORIZATION_BASE_URL = os.getenv('AUTHORIZATION_BASE_URL')
    TOKEN_URL = os.getenv('TOKEN_URL')
    SCOPE = os.getenv('SCOPE').split(',')

Create a .env file with your Discord credentials.

CLIENT_ID=your_client_id_here
CLIENT_SECRET=your_client_secret_here
DISCORD_REDIRECT_URI=http://127.0.0.1:5000/callback
AUTHORIZATION_BASE_URL=https://discord.com/api/oauth2/authorize
TOKEN_URL=https://discord.com/api/v10/oauth2/token
SCOPE=identify,email

Implementing the Flask Application

Here's the complete implementation (app.py) with explanations for each part.

from flask import Flask, redirect, request, session
from requests_oauthlib import OAuth2Session
import os
from dotenv import load_dotenv
import requests
from flasgger import Swagger
from config import Config

load_dotenv()
app = Flask(__name__)
app.secret_key = os.urandom(24)

# Swagger configuration for API documentation
app.config['SWAGGER'] = {
    'title': 'Discord Login API',
    'uiversion': 3
}
swagger = Swagger(app)

1. Login Route

This route initiates the OAuth2 flow by redirecting users to Discord's authorization page.

@app.get('/login')
def login():
    """
    Redirects user to Discord for authorization.
    ---
    responses:
      302:
        description: Redirect to Discord's authorization page.
    """
    discord = OAuth2Session(
        Config.CLIENT_ID,
        redirect_uri=Config.REDIRECT_URI,
        scope=Config.SCOPE
    )
    authorization_url, state = discord.authorization_url(Config.AUTHORIZATION_BASE_URL)
    session['oauth_state'] = state
    return redirect(authorization_url)

2. Callback Route

After Discord authorization, users are redirected here. This route exchanges the authorization code for an access token.

@app.get("/callback/")
def callback():
    """
    Handles the callback from Discord, exchanges the code for an access token.
    ---
    parameters:
      - name: code
        in: query
        type: string
        required: true
        description: The authorization code received from Discord.
      - name: state
        in: query
        type: string
        required: true
        description: The state returned by Discord.
    responses:
      200:
        description: Returns the access token and other data.
    """
    code = request.args.get('code')
    state = request.args.get('state')

    data = {
        'grant_type': 'authorization_code',
        'code': code,
        'redirect_uri': Config.REDIRECT_URI
    }

    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }

    r = requests.post(
        Config.TOKEN_URL,
        data=data,
        headers=headers,
        auth=(Config.CLIENT_ID, Config.CLIENT_SECRET)
    )
    r.raise_for_status()
    
    return r.json()

3. User Info Route

This endpoint fetches user information using the obtained access token.

@app.get('/getuserinfo/')
def get_userinfo():
    """
    Retrieves user information from Discord using the access token.
    ---
    parameters:
      - name: access_token
        in: query
        type: string
        required: true
        description: The access token obtained from the callback.
    responses:
      200:
        description: Returns the user information.
    """
    access_token = request.args.get('access_token')
    headers = {
        'Authorization': f'Bearer {access_token}',
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    r = requests.get('https://discord.com/api/v10/users/@me', headers=headers)
    r.raise_for_status()
    user = r.json()
    user['avatar'] = get_user_avatar(user)
    return user

4. Avatar Helper Function

This function constructs the proper URL for the user's avatar.

def get_user_avatar(user):
    """Get user profile image"""
    if user['avatar'] is None:
        return "https://cdn.discordapp.com/embed/avatars/0.png"
    return f"https://cdn.discordapp.com/avatars/{user['id']}/{user['avatar']}.png?size=1024"

Running the Application

Start the Flask development server.

python3 app.py

Testing the Implementation

  1. Visit http://localhost:5000/login in your browser.
  2. You'll be redirected to Discord's authorization page.
  3. After authorization, you'll be redirected back to your callback URL with an access token.
  4. Use Swagger UI at http://localhost:5000/apidocs to test the /getuserinfo/ endpoint.

GitHub Repository

You can find a complete implementation of this Discord login sample at: GitHub Repository Link