@glitch_protocol/auth-server
v0.2.0
Published
glitch_protocol engine and Express adapter for Node.js backends
Downloads
459
Maintainers
Readme
@glitch_protocol/auth-server
Core authentication engine (glitch_protocolEngine) with session management, JWT token handling, and support for pluggable database/cache/socket adapters.
Install
npm install @glitch_protocol/auth-server @glitch_protocol/auth-adapter-drizzle @glitch_protocol/auth-adapter-redisAPI
glitch_protocolEngine Constructor
new glitch_protocolEngine(
db: DatabaseAdapter,
cache: CacheAdapter,
socket: SocketBroadcaster,
config: glitch_protocolConfig,
)Config:
interface glitch_protocolConfig {
jwtSecret: string; // Min 32 chars. Signs access tokens.
accessTokenExpiryMinutes?: number; // Default: 15
refreshTokenExpiryDays?: number; // Default: 30
bcryptRounds?: number; // Default: 10 (for password hashing)
}Example:
import { glitch_protocolEngine } from "@glitch_protocol/auth-server";
import { DrizzleAdapter } from "@glitch_protocol/auth-adapter-drizzle";
import { RedisCacheAdapter } from "@glitch_protocol/auth-adapter-redis";
const engine = new glitch_protocolEngine(
new DrizzleAdapter(db, { users, sessions, refreshTokens }),
new RedisCacheAdapter(redis),
socketBroadcaster,
{ jwtSecret: process.env.JWT_SECRET! },
);Token Methods
generateTokens
async generateTokens(userId: string, sessionId: string): Promise<TokenPair>Generates a new access/refresh token pair. Called after login, registration, and token rotation.
rotateRefreshToken
async rotateRefreshToken(rawRefreshToken: string): Promise<{
tokens: TokenPair;
userId: string;
sessionId: string;
}>Rotates a refresh token: validates, checks reuse, revokes old token, generates new pair. Security: if a revoked token is reused, all user sessions are revoked immediately.
verifyAccessToken
verifyAccessToken(token: string): { sub: string; sessionId: string }Verifies a JWT access token. Throws if expired or invalid.
hashPassword
async hashPassword(password: string): Promise<string>Hashes a password using bcryptjs. Use before storing in DB.
comparePassword
async comparePassword(password: string, hash: string): Promise<boolean>Compares a plaintext password against a hash.
Session Methods
createSession
async createSession(
userId: string,
ipAddress: string,
userAgent: string | undefined,
): Promise<{ session: SessionInfo; tokens: TokenPair }>Creates a new session after login. Generates tokens, broadcasts SESSION_NEW event to all user devices, and invalidates the sessions cache.
getActiveSessions
async getActiveSessions(userId: string): Promise<SessionInfo[]>Gets all active sessions for a user (cached). Used to display "your devices" list.
terminateSession
async terminateSession(
sessionId: string,
userId: string,
reason?: string,
): Promise<void>Terminates a specific session. Deactivates in DB, caches the deactivation, revokes tokens, broadcasts termination event to the terminated device, and updates session list for all devices.
updateSessionActivity
async updateSessionActivity(sessionId: string): Promise<void>Updates a session's lastActivity timestamp (debounced via cache). Called on every request.
updateSessionSocketId
async updateSessionSocketId(sessionId: string, socketId: string | null): Promise<void>Associates a session with a Socket.IO socket ID for real-time events.
terminateAllOtherSessions
async terminateAllOtherSessions(
userId: string,
currentSessionId: string,
): Promise<void>Terminates all other sessions for a user (except current). Enables "log out all other devices".
Cleanup & Lifecycle
cleanupExpiredSessions
async cleanupExpiredSessions(): Promise<void>Revokes expired sessions and tokens. Runs under distributed lock (only one instance per cluster runs this). Called by startCleanupJob() every 15 minutes.
startCleanupJob
startCleanupJob(intervalMs?: number): voidStarts the cleanup job. Default interval: 900000ms (15 minutes). Should be called once at server startup.
stopCleanupJob
stopCleanupJob(): voidStops the cleanup job. Should be called on graceful shutdown (SIGTERM/SIGINT).
Database Access
getDatabaseAdapter
get getDatabaseAdapter(): DatabaseAdapterProvides direct access to the database adapter for operations not exposed through the engine's public API.
Error Classes
TokenError
class TokenError extends Error {
code: string;
}Thrown by token operations. Check error.code against ERROR_CODES.TOKEN_* constants.
SessionError
class SessionError extends Error {
code: string;
}Thrown by session operations. Check error.code against ERROR_CODES.SESSION_* constants.
Error factory functions:
createTokenExpiredError(): TokenError
createTokenInvalidError(): TokenError
createTokenReuseError(): TokenError // Token compromise detected
createSessionNotFoundError(): SessionError
createSessionExpiredError(): SessionError
createUserNotFoundError(): SessionErrorUtilities
noopCache
const noopCache: CacheAdapter = {
get: async () => null,
set: async () => {},
delete: async () => {},
deleteMany: async () => {},
acquireLock: async () => true,
releaseLock: async () => {},
isAvailable: async () => false,
};In-memory fallback cache. Use only for single-instance dev. For production, use @glitch_protocol/auth-adapter-redis.
Express Integration
Add the engine to your app:
import express from "express";
import { engine } from "./engine";
const app = express();
app.post("/api/auth/login", async (req, res) => {
const { email, password } = req.body;
const user = await findUser(email);
if (!user || !(await engine.comparePassword(password, user.passwordHash))) {
return res.status(401).json({ success: false, error: "Invalid credentials" });
}
const { session, tokens } = await engine.createSession(
user.id,
req.ip ?? "unknown",
req.headers["user-agent"],
);
// Set httpOnly refresh token cookie
res.cookie("refreshToken", tokens.refreshToken, {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 30 * 24 * 60 * 60 * 1000,
});
res.json({
success: true,
data: {
accessToken: tokens.accessToken,
user: { id: user.id, email: user.email, name: user.name },
sessions: [session],
},
});
});
app.post("/api/auth/refresh", async (req, res) => {
try {
const { tokens } = await engine.rotateRefreshToken(req.cookies.refreshToken);
res.cookie("refreshToken", tokens.refreshToken, { httpOnly: true, secure: true, sameSite: "strict" });
res.json({ success: true, data: { accessToken: tokens.accessToken } });
} catch (error) {
res.status(401).json({ success: false, error: "Refresh failed", code: error.code });
}
});
app.post("/api/sessions/terminate", async (req, res) => {
const { sessionId } = req.body;
const userId = extractUserIdFromJWT(req.headers.authorization);
await engine.terminateSession(sessionId, userId);
res.json({ success: true });
});
// Startup
engine.startCleanupJob();
process.on("SIGTERM", () => engine.stopCleanupJob());Peer Dependencies
@glitch_protocol/auth-core— interface definitions@glitch_protocol/auth-adapter-drizzleor similar — database adapter@glitch_protocol/auth-adapter-redisornoopCache— cache adapter
Node Version
ESM-only. Requires Node.js >=18.
