Epilogue Auth Docs

Node.js / Express

Implement Epilogue Auth with Node.js and Express

Installation

npm init -y
npm install express prisma @prisma/client

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 Schema (Prisma)

model User {
  id           String    @id @default(cuid())
  authUserId   String    @unique
  name         String
  profileImage String?
  sessions     Session[]
}

model Session {
  id        String   @id @default(cuid())
  token     String   @unique
  userId    String
  user      User     @relation(fields: [userId], references: [id])
  expiresAt DateTime
}

Implementation

Helper Function

// lib/randomString.js
function generateRandomString(length) {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let result = '';
  for (let i = 0; i < length; i++) {
    result += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return result;
}

module.exports = generateRandomString;

Auth Routes

// routes/auth.js
const generateRandomString = require("../lib/randomString");

module.exports = (app, prisma) => {
    // Start authentication - redirect to Epilogue Auth
    app.get("/api/auth/start", async (req, res) => {
        res.redirect(process.env.AUTH_URL);
    });

    // Handle callback from Epilogue Auth
    app.get("/api/auth/callback", async (req, res) => {
        const code = req.query.code;
        if (!code) {
            return res.status(400).json({ error: "Missing code" });
        }

        try {
            // Exchange code for token
            const tokenResponse = await fetch(
                `https://auth.epilogue.team/api/v1/authorize/${process.env.APP_ID}`,
                {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify({
                        authorizationCode: code,
                        applicationSecret: process.env.APP_SECRET
                    }),
                }
            );
            const { token } = await tokenResponse.json();

            // Fetch user info
            const userResponse = await fetch("https://auth.epilogue.team/api/v1/app/me", {
                method: "GET",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${token}`,
                },
            });
            const userData = await userResponse.json();

            if (userData.error) {
                throw new Error(userData.error);
            }

            // Find or create user
            let user = await prisma.user.findUnique({
                where: { authUserId: userData.id },
            });

            if (!user) {
                user = await prisma.user.create({
                    data: {
                        authUserId: userData.id,
                        name: userData.username || `user_${userData.id}`,
                        profileImage: userData.iconUrl || null,
                    }
                });
            }

            // Create session
            const sessionToken = generateRandomString(200);
            await prisma.session.create({
                data: {
                    token: sessionToken,
                    userId: user.id,
                    expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24),
                }
            });

            // Return token to client
            res.send(`
                <script>
                    localStorage.setItem("token", "${sessionToken}");
                    window.location.href = "/";
                </script>
            `);

        } catch (error) {
            console.error(error);
            res.status(500).json({ error: "Internal server error" });
        }
    });

    // Get current user
    app.get("/api/auth/me", async (req, res) => {
        const token = req.headers.authorization?.replace('Bearer ', '') || req.query.token;
        
        if (!token) {
            return res.status(401).json({ error: 'Authentication required' });
        }

        const session = await prisma.session.findFirst({
            where: {
                token: token,
                expiresAt: { gt: new Date() }
            },
            include: { user: true }
        });

        if (!session) {
            return res.status(401).json({ error: 'Invalid or expired token' });
        }

        res.json({
            id: session.user.id,
            name: session.user.name,
            profileImage: session.user.profileImage
        });
    });
};

Main App

// app.js
require('dotenv').config();
const express = require('express');
const { PrismaClient } = require('@prisma/client');

const app = express();
const prisma = new PrismaClient();

app.use(express.json());

// Register auth routes
require('./routes/auth')(app, prisma);

app.listen(3000, () => {
    console.log('Server running on http://localhost:3000');
});