AgentStack
SKILL verified MIT Self-run

Better Auth

skill-allanninal-claude-code-skills-better-auth · by allanninal

Better Auth integration patterns for authentication in TypeScript applications. Use when implementing authentication, sessions, OAuth, or user management.

No reviews yet
0 installs
7 views
0.0% view→install

Install

$ agentstack add skill-allanninal-claude-code-skills-better-auth

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Better Auth? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Better Auth Integration

When to Use This Skill

  • Setting up authentication in TypeScript apps
  • Implementing OAuth providers (Google, GitHub, etc.)
  • Managing user sessions
  • Building login/signup flows
  • Handling password reset and email verification

Setup

Installation

npm install better-auth
# or
pnpm add better-auth

Basic Configuration

// lib/auth.ts
import { betterAuth } from 'better-auth';
import { prismaAdapter } from 'better-auth/adapters/prisma';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export const auth = betterAuth({
  database: prismaAdapter(prisma, {
    provider: 'postgresql', // or 'mysql', 'sqlite'
  }),
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
  },
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7 days
    updateAge: 60 * 60 * 24, // 1 day
  },
});

Database Schema (Prisma)

// prisma/schema.prisma
model User {
  id            String    @id @default(cuid())
  email         String    @unique
  emailVerified DateTime?
  name          String?
  image         String?
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt

  sessions Session[]
  accounts Account[]
}

model Session {
  id        String   @id @default(cuid())
  userId    String
  expiresAt DateTime
  token     String   @unique
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model Account {
  id                String  @id @default(cuid())
  userId            String
  accountId         String
  providerId        String
  accessToken       String?
  refreshToken      String?
  accessTokenExpiresAt DateTime?
  refreshTokenExpiresAt DateTime?
  scope             String?
  idToken           String?
  createdAt         DateTime @default(now())
  updatedAt         DateTime @updatedAt

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([providerId, accountId])
}

model Verification {
  id         String   @id @default(cuid())
  identifier String
  value      String
  expiresAt  DateTime
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt

  @@unique([identifier, value])
}

OAuth Providers

Configure Providers

// lib/auth.ts
import { betterAuth } from 'better-auth';

export const auth = betterAuth({
  // ... database config
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
    discord: {
      clientId: process.env.DISCORD_CLIENT_ID!,
      clientSecret: process.env.DISCORD_CLIENT_SECRET!,
    },
  },
});

OAuth Callback URLs

Google: http://localhost:3000/api/auth/callback/google
GitHub: http://localhost:3000/api/auth/callback/github
Discord: http://localhost:3000/api/auth/callback/discord

API Routes

Next.js App Router

// app/api/auth/[...all]/route.ts
import { auth } from '@/lib/auth';
import { toNextJsHandler } from 'better-auth/next-js';

export const { GET, POST } = toNextJsHandler(auth.handler);

Express

// routes/auth.ts
import express from 'express';
import { auth } from '../lib/auth';
import { toNodeHandler } from 'better-auth/node';

const router = express.Router();

router.all('/api/auth/*', toNodeHandler(auth.handler));

export default router;

Client Integration

React Client

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

export const authClient = createAuthClient({
  baseURL: process.env.NEXT_PUBLIC_APP_URL,
});

export const { signIn, signUp, signOut, useSession } = authClient;

Usage in Components

// components/AuthButtons.tsx
'use client';

import { signIn, signOut, useSession } from '@/lib/auth-client';

export function AuthButtons() {
  const { data: session, isPending } = useSession();

  if (isPending) {
    return Loading...;
  }

  if (session) {
    return (
      
        Welcome, {session.user.name}
         signOut()}>Sign Out
      
    );
  }

  return (
    
       signIn.social({ provider: 'google' })}>
        Sign in with Google
      
       signIn.social({ provider: 'github' })}>
        Sign in with GitHub
      
    
  );
}

Email/Password Auth

// components/LoginForm.tsx
'use client';

import { signIn, signUp } from '@/lib/auth-client';
import { useState } from 'react';

export function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [isSignUp, setIsSignUp] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    if (isSignUp) {
      const result = await signUp.email({
        email,
        password,
        name: email.split('@')[0],
      });

      if (result.error) {
        console.error(result.error);
      }
    } else {
      const result = await signIn.email({ email, password });

      if (result.error) {
        console.error(result.error);
      }
    }
  };

  return (
    
       setEmail(e.target.value)}
        placeholder="Email"
        required
      />
       setPassword(e.target.value)}
        placeholder="Password"
        required
      />
      {isSignUp ? 'Sign Up' : 'Sign In'}
       setIsSignUp(!isSignUp)}>
        {isSignUp ? 'Have an account? Sign in' : 'Need an account? Sign up'}
      
    
  );
}

Server-Side Auth

Get Session in Server Components

// app/dashboard/page.tsx
import { auth } from '@/lib/auth';
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
  const session = await auth.api.getSession({
    headers: headers(),
  });

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

  return (
    
      Dashboard
      Welcome, {session.user.name}
    
  );
}

Middleware Protection

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { auth } from '@/lib/auth';

export async function middleware(request: NextRequest) {
  const session = await auth.api.getSession({
    headers: request.headers,
  });

  if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};

Advanced Features

Email Verification

// lib/auth.ts
import { betterAuth } from 'better-auth';
import { sendVerificationEmail } from './email';

export const auth = betterAuth({
  // ... config
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    sendVerificationEmail: async ({ user, url }) => {
      await sendVerificationEmail({
        to: user.email,
        subject: 'Verify your email',
        html: `Click to verify`,
      });
    },
  },
});

Password Reset

// lib/auth.ts
export const auth = betterAuth({
  // ... config
  emailAndPassword: {
    enabled: true,
    sendResetPassword: async ({ user, url }) => {
      await sendEmail({
        to: user.email,
        subject: 'Reset your password',
        html: `Reset password`,
      });
    },
  },
});

// Client usage
import { authClient } from '@/lib/auth-client';

await authClient.forgetPassword({ email: 'user@example.com' });
await authClient.resetPassword({ token, newPassword });

Two-Factor Authentication

// lib/auth.ts
import { betterAuth } from 'better-auth';
import { twoFactor } from 'better-auth/plugins';

export const auth = betterAuth({
  // ... config
  plugins: [
    twoFactor({
      issuer: 'My App',
    }),
  ],
});

// Client
const { twoFactor } = authClient;

// Enable 2FA
const { totpURI } = await twoFactor.enable();

// Verify 2FA
await twoFactor.verify({ code: '123456' });

Best Practices

  • [ ] Always use HTTPS in production
  • [ ] Store secrets in environment variables
  • [ ] Enable email verification for new accounts
  • [ ] Implement rate limiting on auth endpoints
  • [ ] Use secure session settings
  • [ ] Add CSRF protection
  • [ ] Log authentication events
  • [ ] Implement account lockout after failed attempts

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.