Node.js / Express
Implement Epilogue Auth with Node.js and Express
Installation
npm init -y
npm install express prisma @prisma/clientEnvironment Variables
Create a .env file:
AUTH_URL=https://auth.epilogue.team/authorize/your-app-id
APP_ID=your-app-id
APP_SECRET=your-app-secretDatabase 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');
});