> ## 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.

# Authenticate users in EdgeSpark

> Access the current user with auth.user, protect API routes, handle optional auth in public routes, and verify webhook signatures in EdgeSpark.

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`](/sdk/web) and the [auth UI guide](/guides/auth-ui).

## Read the current user

```typescript server/src/index.ts theme={null}
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

```typescript server/src/index.ts theme={null}
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

```typescript server/src/index.ts theme={null}
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:

```typescript server/src/index.ts theme={null}
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

<Columns cols={2}>
  <Card title="auth reference" icon="code" href="/sdk/auth">
    The runtime auth API and when `auth.user` is available.
  </Card>

  <Card title="Path-based auth" icon="lock" href="/concepts/path-based-auth">
    How URL path conventions enforce login requirements without middleware.
  </Card>
</Columns>
