Node.js HTTP Adapter

Using @warpy-auth-sdk/core with pure Node.js HTTP

Node.js HTTP Adapter

The Node.js HTTP adapter provides integration of @warpy-auth-sdk/core with the native Node.js http module. This adapter has zero framework dependencies and gives you maximum control and flexibility.

Installation

npm install @warpy-auth-sdk/core
# or
yarn add @warpy-auth-sdk/core
# or
pnpm add @warpy-auth-sdk/core

No additional dependencies required!

Quick Start

Basic Setup

import { createServer } from "http";
import { createAuthHandler } from "@warpy-auth-sdk/core/adapters/node";
import { google } from "@warpy-auth-sdk/core";

// Configure authentication
const { handler, requireAuth } = createAuthHandler({
  secret: process.env.AUTH_SECRET!,
  provider: google({
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    redirectUri: "http://localhost:3000/api/auth/callback/google",
  }),
}, {
  basePath: "/api/auth",
  successRedirect: "/dashboard",
  errorRedirect: "/login",
});

// Create HTTP server
const server = createServer(async (req, res) => {
  // Try auth handler first
  const handled = await handler(req, res);
  if (handled) return;

  // Your custom routes
  if (req.url === "/api/user" && req.method === "GET") {
    const authenticated = await requireAuth(req, res);
    if (!authenticated) return; // Already sent 401

    res.writeHead(200, { "content-type": "application/json" });
    res.end(JSON.stringify({ user: req.session.user }));
    return;
  }

  // 404
  res.writeHead(404);
  res.end("Not Found");
});

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

API Reference

createAuthHandler(config, options)

Creates an authentication handler and middleware for Node.js HTTP servers.

Parameters

  • config (AuthConfig): Authentication configuration
    • secret: JWT signing secret (min 32 characters)
    • provider: OAuth provider (Google, GitHub, etc.)
    • adapter: Optional database adapter
    • callbacks: Optional callbacks
  • options (NodeAuthOptions): Optional configuration
    • basePath: Base path for auth routes (default: /auth)
    • successRedirect: Redirect after successful auth (default: /)
    • errorRedirect: Redirect after auth failure (default: /login)
    • mcp: MCP configuration

Returns

An object with:

  • handler: Async function to handle auth routes
  • requireAuth: Middleware function to protect routes

Handler Routes

When a request matches these routes, the handler returns true:

  • GET {basePath}/session - Get current session
  • POST {basePath}/signout - Sign out user
  • GET {basePath}/signin/:provider - Provider-specific sign-in
  • GET {basePath}/callback/:provider - Provider-specific callback
  • POST {basePath}/mcp - MCP tools endpoint (if enabled)

Usage Examples

Protecting Routes

const server = createServer(async (req, res) => {
  const handled = await handler(req, res);
  if (handled) return;

  // Protected API endpoint
  if (req.url === "/api/user" && req.method === "GET") {
    const authenticated = await requireAuth(req, res);
    if (!authenticated) return; // requireAuth sends 401 response

    // Access session from req.session
    res.writeHead(200, { "content-type": "application/json" });
    res.end(JSON.stringify({ user: req.session.user }));
    return;
  }

  // Public endpoint
  if (req.url === "/api/health") {
    res.writeHead(200, { "content-type": "application/json" });
    res.end(JSON.stringify({ status: "ok" }));
    return;
  }

  res.writeHead(404);
  res.end("Not Found");
});

Serving Static Files

import { readFileSync } from "fs";
import { join } from "path";

const server = createServer(async (req, res) => {
  const handled = await handler(req, res);
  if (handled) return;

  const url = new URL(req.url!, `http://${req.headers.host}`);

  // Serve HTML
  if (url.pathname === "/") {
    const html = readFileSync(join(__dirname, "public/index.html"));
    res.writeHead(200, { "content-type": "text/html" });
    res.end(html);
    return;
  }

  // Serve static assets
  if (url.pathname.startsWith("/public/")) {
    try {
      const file = readFileSync(join(__dirname, url.pathname));
      res.writeHead(200);
      res.end(file);
      return;
    } catch {
      res.writeHead(404);
      res.end("Not Found");
    }
  }
});

Custom Callbacks

const { handler, requireAuth } = createAuthHandler({
  secret: process.env.AUTH_SECRET!,
  provider: google({ /* config */ }),
  callbacks: {
    async user(oauthUser) {
      // Create or update user in your database
      return await db.user.findOrCreate({
        where: { email: oauthUser.email },
        defaults: {
          name: oauthUser.name,
          picture: oauthUser.picture,
        },
      });
    },
    jwt(token) {
      token.role = "user";
      return token;
    },
    session(session) {
      session.customField = "value";
      return session;
    },
  },
});

With Database Adapter

import { PrismaAdapter } from "@warpy-auth-sdk/core";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

const { handler, requireAuth } = createAuthHandler({
  secret: process.env.AUTH_SECRET!,
  provider: google({ /* config */ }),
  adapter: PrismaAdapter(prisma),
});

MCP (AI Agent) Support

const { handler, requireAuth } = createAuthHandler({
  secret: process.env.AUTH_SECRET!,
  provider: google({ /* config */ }),
}, {
  basePath: "/api/auth",
  mcp: {
    enabled: true,
    path: "/api/mcp",
    shield: {
      secret: process.env.AUTH_SECRET!,
      warpy: {
        apiKey: process.env.WARPY_API_KEY,
      },
    },
  },
});

// MCP tools now available at POST /api/mcp

URL Routing

import { parse } from "url";

const server = createServer(async (req, res) => {
  const handled = await handler(req, res);
  if (handled) return;

  const { pathname } = parse(req.url || "/", true);

  // Route matching
  switch (pathname) {
    case "/":
      // Home page
      break;
    case "/api/user":
      const authenticated = await requireAuth(req, res);
      if (!authenticated) return;
      // Handle user API
      break;
    default:
      res.writeHead(404);
      res.end("Not Found");
  }
});

Request Body Parsing

async function parseJSON(req: IncomingMessage): Promise<any> {
  return new Promise((resolve, reject) => {
    let body = "";
    req.on("data", (chunk) => {
      body += chunk.toString();
    });
    req.on("end", () => {
      try {
        resolve(JSON.parse(body));
      } catch (err) {
        reject(err);
      }
    });
  });
}

const server = createServer(async (req, res) => {
  const handled = await handler(req, res);
  if (handled) return;

  if (req.url === "/api/data" && req.method === "POST") {
    try {
      const body = await parseJSON(req);
      res.writeHead(200, { "content-type": "application/json" });
      res.end(JSON.stringify({ received: body }));
    } catch {
      res.writeHead(400);
      res.end("Invalid JSON");
    }
  }
});

TypeScript Support

The adapter includes full TypeScript support:

import type {
  IncomingMessage,
  ServerResponse,
  NodeAuthOptions,
  NodeAuthMiddleware,
  Session,
} from "@warpy-auth-sdk/core/adapters/node";

// Extend request type with session
declare module "http" {
  interface IncomingMessage {
    session?: Session;
  }
}

// Typed handler
const server = createServer(
  async (req: IncomingMessage, res: ServerResponse) => {
    // ...
  }
);

Complete Example

See the complete Node.js example in the repository.

Comparison with Other Adapters

FeatureNode.jsExpressFastifyHono
Framework DependencyNoneYesYesYes
Bundle SizeSmallestMediumMediumSmall
Learning CurveSteepEasyEasyEasy
FlexibilityMaximumHighHighHigh
TypeScript Support✓✓✓✓
MCP Support✓✓✓✓
Best ForMicroservices, ServerlessTraditional appsHigh performanceMulti-runtime

Benefits of Pure Node.js

  1. Zero Dependencies: No framework overhead
  2. Maximum Control: Full control over request/response handling
  3. Smallest Bundle: Minimal production footprint
  4. Learning: Understand HTTP fundamentals
  5. Flexibility: Easy to integrate with any HTTP server

When to Use

Choose the Node.js adapter when:

  • Building microservices with minimal dependencies
  • Deploying to serverless/edge environments
  • Need maximum performance
  • Want full control over HTTP handling
  • Learning HTTP fundamentals

Choose a framework adapter when:

  • Building traditional web applications
  • Need rich middleware ecosystem
  • Want faster development
  • Team is familiar with the framework

Troubleshooting

Session not persisting

The adapter automatically handles cookies. Ensure your client sends cookies:

fetch("/api/user", {
  credentials: "include", // Important!
});

Handler not catching routes

Make sure you call the handler before your custom routes:

const server = createServer(async (req, res) => {
  // Call handler FIRST
  const handled = await handler(req, res);
  if (handled) return;

  // Then your routes
  // ...
});

Request body issues

The adapter handles body parsing for MCP endpoints. For custom endpoints, parse manually:

async function readBody(req): Promise<string> {
  return new Promise((resolve) => {
    let body = "";
    req.on("data", (chunk) => body += chunk);
    req.on("end", () => resolve(body));
  });
}

CORS issues

Add CORS headers manually:

const server = createServer(async (req, res) => {
  // CORS headers
  res.setHeader("Access-Control-Allow-Origin", "http://localhost:5173");
  res.setHeader("Access-Control-Allow-Credentials", "true");
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");

  if (req.method === "OPTIONS") {
    res.writeHead(204);
    res.end();
    return;
  }

  const handled = await handler(req, res);
  // ...
});

Next Steps