Epilogue Auth Docs

Python / Flask

Implement Epilogue Auth with Python and Flask

Installation

pip install flask requests python-dotenv sqlalchemy

Environment Variables

Create a .env file:

AUTH_URL=https://auth.epilogue.team/authorize/your-app-id
APP_ID=your-app-id
APP_SECRET=your-app-secret
DATABASE_URL=sqlite:///app.db

Models

# models.py
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import uuid

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    auth_user_id = db.Column(db.String(255), unique=True, nullable=False)
    name = db.Column(db.String(255), nullable=False)
    profile_image = db.Column(db.String(500), nullable=True)
    sessions = db.relationship('Session', backref='user', lazy=True)

class Session(db.Model):
    id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    token = db.Column(db.String(255), unique=True, nullable=False)
    user_id = db.Column(db.String(36), db.ForeignKey('user.id'), nullable=False)
    expires_at = db.Column(db.DateTime, nullable=False)

Auth Routes

# routes/auth.py
import os
import secrets
import string
import requests
from datetime import datetime, timedelta
from flask import Blueprint, redirect, request, jsonify

auth_bp = Blueprint('auth', __name__, url_prefix='/api/auth')

def generate_random_string(length=200):
    alphabet = string.ascii_letters + string.digits
    return ''.join(secrets.choice(alphabet) for _ in range(length))

@auth_bp.route('/start')
def start():
    """Redirect to Epilogue Auth"""
    return redirect(os.environ['AUTH_URL'])

@auth_bp.route('/callback')
def callback():
    """Handle callback from Epilogue Auth"""
    from models import db, User, Session
    
    code = request.args.get('code')
    if not code:
        return jsonify({'error': 'Missing code'}), 400

    try:
        # Exchange code for token
        token_response = requests.post(
            f"https://auth.epilogue.team/api/v1/authorize/{os.environ['APP_ID']}",
            json={
                'authorizationCode': code,
                'applicationSecret': os.environ['APP_SECRET']
            },
            headers={'Content-Type': 'application/json'}
        )
        token_data = token_response.json()
        token = token_data['token']

        # Fetch user info
        user_response = requests.get(
            'https://auth.epilogue.team/api/v1/app/me',
            headers={
                'Content-Type': 'application/json',
                'Authorization': f'Bearer {token}'
            }
        )
        user_data = user_response.json()

        if 'error' in user_data:
            raise Exception(user_data['error'])

        # Find or create user
        user = User.query.filter_by(auth_user_id=user_data['id']).first()
        
        if not user:
            user = User(
                auth_user_id=user_data['id'],
                name=user_data.get('username') or f"user_{user_data['id']}",
                profile_image=user_data.get('iconUrl')
            )
            db.session.add(user)
            db.session.commit()

        # Create session
        session_token = generate_random_string(200)
        session = Session(
            token=session_token,
            user_id=user.id,
            expires_at=datetime.utcnow() + timedelta(days=1)
        )
        db.session.add(session)
        db.session.commit()

        # Return token to client
        return f'''
            <html><body><script>
                localStorage.setItem("token", "{session_token}");
                window.location.href = "/";
            </script></body></html>
        '''

    except Exception as e:
        print(f"Error: {e}")
        return jsonify({'error': 'Internal server error'}), 500

@auth_bp.route('/me')
def me():
    """Get current user info"""
    from models import Session
    
    auth_header = request.headers.get('Authorization', '')
    token = auth_header.replace('Bearer ', '') or request.args.get('token')
    
    if not token:
        return jsonify({'error': 'Authentication required'}), 401

    session = Session.query.filter(
        Session.token == token,
        Session.expires_at > datetime.utcnow()
    ).first()

    if not session:
        return jsonify({'error': 'Invalid or expired token'}), 401

    return jsonify({
        'id': session.user.id,
        'name': session.user.name,
        'profileImage': session.user.profile_image
    })

Main App

# app.py
import os
from dotenv import load_dotenv
from flask import Flask

load_dotenv()

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:///app.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

from models import db
db.init_app(app)

from routes.auth import auth_bp
app.register_blueprint(auth_bp)

with app.app_context():
    db.create_all()

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

Running the App

python app.py

Visit http://localhost:3000/api/auth/start to begin authentication.