Back to list
oakoss

auth

by oakoss

Open-source SaaS starter kit with React, TanStack, and Better Auth

0🍴 0📅 Jan 26, 2026

SKILL.md


name: auth description: Better Auth authentication. Use for auth, login, logout, session, user, signup, register, protect, middleware, password, oauth, social

For advanced patterns, OAuth configuration, and session middleware, see reference.md.

Better Auth

Package Structure

packages/auth/
├── src/
│   ├── client.ts      # Auth client (React hooks)
│   ├── server.ts      # Auth server (Better Auth config)
│   └── index.ts       # Public exports
└── package.json

Server Configuration

// packages/auth/src/server.ts
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { betterAuth } from 'better-auth';
import { tanstackStartCookies } from 'better-auth/tanstack-start';
import { db } from '@oakoss/database';

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: 'pg',
    usePlural: true, // Uses 'users', 'sessions', etc.
  }),
  emailAndPassword: { enabled: true },
  plugins: [tanstackStartCookies()], // Must be last plugin in array
});

Client Configuration

// packages/auth/src/client.ts
import { createAuthClient } from 'better-auth/client';
import { inferAdditionalFields } from 'better-auth/client/plugins';
import { type auth } from '@oakoss/auth/server';

export const authClient = createAuthClient({
  baseURL: process.env.PUBLIC_APP_URL ?? 'http://localhost:3000',
  plugins: [inferAdditionalFields<typeof auth>()],
});

API Route Handler

// apps/web/src/routes/api/auth/$.ts
import { createFileRoute } from '@tanstack/react-router';
import { auth } from '@oakoss/auth/server';

export const Route = createFileRoute('/api/auth/$')({
  server: {
    handlers: {
      GET: ({ request }) => auth.handler(request),
      POST: ({ request }) => auth.handler(request),
    },
  },
});

Sign In/Sign Up

import { authClient } from '@oakoss/auth/client';

// Sign up with email
await authClient.signUp.email({
  name: 'John Doe',
  email: 'john@example.com',
  password: 'password123',
});

// Sign in with email
await authClient.signIn.email({
  email: 'john@example.com',
  password: 'password123',
  callbackURL: '/dashboard',
});

// Sign in with social provider
await authClient.signIn.social({
  provider: 'github',
  callbackURL: '/dashboard',
});

Session Management

// Reactive hook
function UserProfile() {
  const { data: session, isPending } = authClient.useSession();

  if (isPending) return <Spinner />;
  if (!session) return <LoginPrompt />;

  return <div>Welcome, {session.user.name}</div>;
}

// One-time fetch
const { data: session } = await authClient.getSession();

Route Protection (beforeLoad)

// apps/web/src/routes/_app/route.tsx
import { createFileRoute, redirect } from '@tanstack/react-router';
import { auth } from '@oakoss/auth/server';

export const Route = createFileRoute('/_app')({
  beforeLoad: async ({ context }) => {
    const session = await auth.api.getSession({
      headers: context.request.headers,
    });

    if (!session) {
      throw redirect({ to: '/login' });
    }

    return { user: session.user };
  },
  component: AppLayout,
});

Server Function Auth Check

import { createServerFn } from '@tanstack/react-start';
import { auth } from '@oakoss/auth/server';

const deletePost = createServerFn({ method: 'POST' })
  .inputValidator(z.object({ id: z.string() }))
  .handler(async ({ data, request }) => {
    const session = await auth.api.getSession({ headers: request.headers });

    if (!session) {
      return { error: 'Unauthorized', code: 'AUTH_REQUIRED' };
    }

    // Check ownership
    const post = await db.query.posts.findFirst({
      where: eq(posts.id, data.id),
    });

    if (post?.authorId !== session.user.id) {
      return { error: 'Not authorized', code: 'FORBIDDEN' };
    }

    await db.delete(posts).where(eq(posts.id, data.id));
    return { success: true };
  });

Sign Out

await authClient.signOut({
  fetchOptions: {
    onSuccess: () => (window.location.href = '/login'),
  },
});

Common Mistakes

MistakeCorrect Pattern
Client-side auth checks onlyValidate session server-side in beforeLoad
Missing request headersPass { headers: request.headers } to API
Not handling loading statesCheck isPending before rendering
Hardcoding callback URLsUse environment variables
Storing session in useStateUse authClient.useSession() hook
Missing auth handler routeCreate /api/auth/$.ts catch-all route
Not using usePlural in adapterSet usePlural: true for Better Auth
Importing server code in clientUse @oakoss/auth/server only server-side

Delegation

  • Route protection: For beforeLoad patterns, see tanstack-router skill
  • Server functions: For auth in server functions, see tanstack-start skill
  • Database: For user queries, see database skill
  • Code review: After implementing auth, delegate to code-reviewer agent

Topic References

  • Auth Reference - Social providers, plugins, session types, error handling

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon