heartwood-auth

📁 autumnsgrove/groveengine 📅 8 days ago
1
总安装量
1
周安装量
#42083
全站排名
安装命令
npx skills add https://github.com/autumnsgrove/groveengine --skill heartwood-auth

Agent 安装分布

replit 1
opencode 1
codex 1
claude-code 1
gemini-cli 1

Skill 文档

Heartwood Auth Integration Skill

When to Activate

Activate this skill when:

  • Adding authentication to a Grove application
  • Protecting admin routes
  • Validating user sessions
  • Setting up OAuth sign-in
  • Integrating with Heartwood (GroveAuth)

Overview

Heartwood is Grove’s centralized authentication service powered by Better Auth.

Domain Purpose
heartwood.grove.place Frontend (login UI)
auth-api.grove.place Backend API

Key Features

  • OAuth Providers: Google
  • Magic Links: Click-to-login emails via Resend
  • Passkeys: WebAuthn passwordless authentication
  • KV-Cached Sessions: Sub-100ms validation
  • Cross-Subdomain SSO: Single session across all .grove.place

Integration Approaches

Option A: Better Auth Client (Recommended)

For new integrations, use Better Auth’s client library:

// src/lib/auth/client.ts
import { createAuthClient } from 'better-auth/client';

export const auth = createAuthClient({
  baseURL: 'https://auth-api.grove.place'
});

// Sign in with Google
await auth.signIn.social({ provider: 'google' });

// Get current session
const session = await auth.getSession();

// Sign out
await auth.signOut();

Option B: Cookie-Based SSO (*.grove.place apps)

For apps on .grove.place subdomains, sessions work automatically via cookies:

// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
  // Check session via Heartwood API
  const sessionCookie = event.cookies.get('better-auth.session_token');

  if (sessionCookie) {
    try {
      const response = await fetch('https://auth-api.grove.place/api/auth/session', {
        headers: {
          Cookie: `better-auth.session_token=${sessionCookie}`
        }
      });

      if (response.ok) {
        const data = await response.json();
        event.locals.user = data.user;
        event.locals.session = data.session;
      }
    } catch {
      // Session invalid or expired
    }
  }

  return resolve(event);
};

Option C: Legacy Token Flow (Backwards Compatible)

For existing integrations using the legacy OAuth flow:

// 1. Redirect to Heartwood login
const params = new URLSearchParams({
  client_id: 'your-client-id',
  redirect_uri: 'https://yourapp.grove.place/auth/callback',
  state: crypto.randomUUID(),
  code_challenge: await generateCodeChallenge(verifier),
  code_challenge_method: 'S256'
});
redirect(302, `https://auth-api.grove.place/login?${params}`);

// 2. Exchange code for tokens (in callback route)
const tokens = await fetch('https://auth-api.grove.place/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: code,
    redirect_uri: 'https://yourapp.grove.place/auth/callback',
    client_id: 'your-client-id',
    client_secret: env.HEARTWOOD_CLIENT_SECRET,
    code_verifier: verifier
  })
}).then(r => r.json());

// 3. Verify token on protected routes
const user = await fetch('https://auth-api.grove.place/verify', {
  headers: { Authorization: `Bearer ${tokens.access_token}` }
}).then(r => r.json());

Protected Routes Pattern

SvelteKit Layout Protection

// src/routes/admin/+layout.server.ts
import { redirect } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ locals }) => {
  if (!locals.user) {
    throw redirect(302, '/auth/login');
  }

  return {
    user: locals.user
  };
};

API Route Protection

// src/routes/api/protected/+server.ts
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ locals }) => {
  if (!locals.user) {
    throw error(401, 'Unauthorized');
  }

  return json({ message: 'Protected data', user: locals.user });
};

Session Validation

Via Better Auth Session Endpoint

async function validateSession(sessionToken: string) {
  const response = await fetch('https://auth-api.grove.place/api/auth/session', {
    headers: {
      Cookie: `better-auth.session_token=${sessionToken}`
    }
  });

  if (!response.ok) return null;

  const data = await response.json();
  return data.session ? data : null;
}

Via Legacy Verify Endpoint

async function validateToken(accessToken: string) {
  const response = await fetch('https://auth-api.grove.place/verify', {
    headers: {
      Authorization: `Bearer ${accessToken}`
    }
  });

  const data = await response.json();
  return data.active ? data : null;
}

Client Registration

To integrate a new app with Heartwood, you need to register it as a client.

1. Generate Client Credentials

# Generate a secure client secret
openssl rand -base64 32
# Example: YKzJChC3RPjZvd1f/OD5zUGAvcouOTXG7maQP1ernCg=

# Hash it for storage (base64url encoding)
echo -n "YOUR_SECRET" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '='

2. Register in Heartwood Database

INSERT INTO clients (id, name, client_id, client_secret_hash, redirect_uris, allowed_origins)
VALUES (
  lower(hex(randomblob(16))),
  'Your App Name',
  'your-app-id',
  'BASE64URL_HASHED_SECRET',
  '["https://yourapp.grove.place/auth/callback"]',
  '["https://yourapp.grove.place"]'
);

3. Set Secrets on Your App

# Set the client secret on your app
wrangler secret put HEARTWOOD_CLIENT_SECRET
# Paste: YKzJChC3RPjZvd1f/OD5zUGAvcouOTXG7maQP1ernCg=

Environment Variables

Variable Description
HEARTWOOD_CLIENT_ID Your registered client ID
HEARTWOOD_CLIENT_SECRET Your client secret (never commit!)

API Endpoints Reference

Better Auth Endpoints (Recommended)

Method Endpoint Purpose
POST /api/auth/sign-in/social OAuth sign-in
POST /api/auth/sign-in/magic-link Magic link sign-in
POST /api/auth/sign-in/passkey Passkey sign-in
GET /api/auth/session Get current session
POST /api/auth/sign-out Sign out

Legacy Endpoints

Method Endpoint Purpose
GET /login Login page
POST /token Exchange code for tokens
GET /verify Validate access token
GET /userinfo Get user info

Best Practices

DO

  • Use Better Auth client for new integrations
  • Validate sessions on every protected request
  • Use httpOnly cookies for token storage
  • Implement proper error handling for auth failures
  • Log out users gracefully when sessions expire

DON’T

  • Store tokens in localStorage (XSS vulnerable)
  • Skip session validation on API routes
  • Hardcode client secrets
  • Ignore token expiration

Cross-Subdomain SSO

All .grove.place apps share the same session cookie automatically:

better-auth.session_token (domain=.grove.place)

Once a user signs in on any Grove property, they’re signed in everywhere.

Troubleshooting

“Session not found” errors

  • Check cookie domain is .grove.place
  • Verify SESSION_KV namespace is accessible
  • Check session hasn’t expired

OAuth callback errors

  • Verify redirect_uri matches registered client
  • Check client_id is correct
  • Ensure client_secret_hash uses base64url encoding

Slow authentication

  • Ensure KV caching is enabled (SESSION_KV binding)
  • Check for cold start issues (Workers may sleep)

Related Resources

  • Heartwood Spec: /Users/autumn/Documents/Projects/GroveAuth/GROVEAUTH_SPEC.md
  • Better Auth Docs: https://better-auth.com
  • Client Setup Guide: /Users/autumn/Documents/Projects/GroveAuth/docs/OAUTH_CLIENT_SETUP.md