Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.edgespark.dev/llms.txt

Use this file to discover all available pages before exploring further.

EdgeSpark includes built-in user authentication. Your server code reads the current user through auth from edgespark/http. For browser login and sign-up flows, use @edgespark/web and the auth UI guide.

Read the current user

server/src/index.ts
import { auth } from "edgespark/http";
import { Hono } from "hono";

const app = new Hono().get("/api/me", (c) => {
  return c.json({
    id: auth.user.id,
    email: auth.user.email,
    name: auth.user.name,
  });
});

export default app;

Store data linked to a user

server/src/index.ts
import { db } from "edgespark";
import { auth } from "edgespark/http";
import { Hono } from "hono";
import { posts } from "@defs";

const app = new Hono().post("/api/posts", async (c) => {
  const body = await c.req.json<{ title: string; content: string }>();

  const [post] = await db
    .insert(posts)
    .values({
      title: body.title,
      content: body.content,
      authorId: auth.user.id,
    })
    .returning();

  return c.json(post, 201);
});

export default app;

Handle optional auth in public routes

server/src/index.ts
import { db } from "edgespark";
import { auth } from "edgespark/http";
import { eq } from "drizzle-orm";
import { Hono } from "hono";
import { likes, posts } from "@defs";

const app = new Hono().get("/api/public/feed", async (c) => {
  const allPosts = await db.select().from(posts).where(eq(posts.published, 1));

  if (auth.user) {
    const liked = await db
      .select({ postId: likes.postId })
      .from(likes)
      .where(eq(likes.userId, auth.user.id));

    const likedIds = new Set(liked.map((item) => item.postId));
    return c.json(allPosts.map((post) => ({ ...post, liked: likedIds.has(post.id) })));
  }

  return c.json(allPosts);
});

export default app;

Verify webhooks yourself

Routes under /api/webhooks/* skip session validation, so verify signatures in your own code:
server/src/index.ts
import { secret } from "edgespark";
import { Hono } from "hono";

const app = new Hono().post("/api/webhooks/stripe", async (c) => {
  const signature = c.req.header("stripe-signature") ?? "";
  const rawBody = await c.req.text();
  const signingSecret = secret.get("STRIPE_WEBHOOK_SECRET") ?? "";

  const encoder = new TextEncoder();
  const key = await crypto.subtle.importKey(
    "raw",
    encoder.encode(signingSecret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["verify"]
  );

  const sigParts = Object.fromEntries(
    signature.split(",").map((part) => part.split("=") as [string, string])
  );
  const signedPayload = `${sigParts.t}.${rawBody}`;
  const valid = await crypto.subtle.verify(
    "HMAC",
    key,
    hexToBytes(sigParts.v1 ?? ""),
    encoder.encode(signedPayload)
  );

  if (!valid) return c.json({ error: "Invalid signature" }, 401);
  return c.json({ received: true });
});

export default app;

function hexToBytes(hex: string): Uint8Array {
  const bytes = new Uint8Array(hex.length / 2);

  for (let i = 0; i < hex.length; i += 2) {
    bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
  }

  return bytes;
}

See also

auth reference

The runtime auth API and when auth.user is available.

Path-based auth

How URL path conventions enforce login requirements without middleware.
Last modified on April 7, 2026