Skip to main content
better-auth is a self-hosted, open-source authentication framework that gives you complete control over your authentication system.

Installation

npm install @mastra/auth-better-auth better-auth

Configuration

1

Create better-auth instance

import { betterAuth } from 'better-auth';

export const auth = betterAuth({
  database: {
    provider: 'postgresql',
    url: process.env.DATABASE_URL!,
  },
  emailAndPassword: {
    enabled: true,
  },
});
2

Import MastraAuthBetterAuth

import { MastraAuthBetterAuth } from '@mastra/auth-better-auth';
import { Mastra } from '@mastra/core';

const mastraAuth = new MastraAuthBetterAuth({
  auth, // Pass your better-auth instance
});

const mastra = new Mastra({
  server: {
    auth: mastraAuth,
  },
});
3

Mount better-auth endpoints

import { Hono } from 'hono';
import { MastraServer } from '@mastra/hono';

const app = new Hono();

// Mount better-auth API routes
app.all('/api/auth/**', async (c) => {
  return auth.handler(c.req.raw);
});

const server = new MastraServer({ mastra, app });

Configuration Options

auth
Auth
required
better-auth instance created with betterAuth()
name
string
default:"better-auth"
Provider name identifier

Complete Example

import { betterAuth } from 'better-auth';
import { Hono } from 'hono';
import { Mastra } from '@mastra/core';
import { MastraAuthBetterAuth } from '@mastra/auth-better-auth';
import { MastraServer } from '@mastra/hono';

// Create better-auth instance
const auth = betterAuth({
  database: {
    provider: 'postgresql',
    url: process.env.DATABASE_URL!,
  },
  emailAndPassword: {
    enabled: true,
  },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
});

// Create Mastra auth provider
const mastraAuth = new MastraAuthBetterAuth({ auth });

// Create Mastra instance
const mastra = new Mastra({
  server: {
    auth: mastraAuth,
  },
});

// Create Hono app
const app = new Hono();

// Mount better-auth endpoints
app.all('/api/auth/**', async (c) => {
  return auth.handler(c.req.raw);
});

// Mount Mastra server (protected)
const server = new MastraServer({ mastra, app });

Authentication Methods

Email/Password

const auth = betterAuth({
  database: {
    provider: 'postgresql',
    url: process.env.DATABASE_URL!,
  },
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
  },
});

Social OAuth

const auth = betterAuth({
  database: {
    provider: 'postgresql',
    url: process.env.DATABASE_URL!,
  },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
  },
});
const auth = betterAuth({
  database: {
    provider: 'postgresql',
    url: process.env.DATABASE_URL!,
  },
  plugins: [
    magicLink({
      sendMagicLink: async ({ email, url }) => {
        // Send email with magic link
        await sendEmail(email, url);
      },
    }),
  ],
});

Client Integration

React Client

import { createAuthClient } from 'better-auth/react';
import { useEffect, useState } from 'react';

const authClient = createAuthClient({
  baseURL: 'http://localhost:3000',
});

export default function App() {
  const [session, setSession] = useState(null);

  useEffect(() => {
    authClient.getSession().then(({ data }) => {
      setSession(data);
    });
  }, []);

  const signIn = async () => {
    await authClient.signIn.email({
      email: '[email protected]',
      password: 'password',
    });
  };

  const signOut = async () => {
    await authClient.signOut();
  };

  if (!session) {
    return <button onClick={signIn}>Sign in</button>;
  }

  return (
    <div>
      <p>Welcome {session.user.email}</p>
      <button onClick={signOut}>Sign out</button>
      <MyMastraComponent />
    </div>
  );
}

Making Authenticated Requests

import { createAuthClient } from 'better-auth/react';

const authClient = createAuthClient({
  baseURL: 'http://localhost:3000',
});

async function callMastraAPI() {
  const { data: session } = await authClient.getSession();
  
  if (!session) {
    throw new Error('Not authenticated');
  }

  const response = await fetch('http://localhost:4111/api/mastra/agents', {
    headers: {
      'Authorization': `Bearer ${session.session.token}`,
      'Cookie': document.cookie, // better-auth also supports cookie-based sessions
    },
  });

  return response.json();
}

User Object

interface BetterAuthUser {
  session: {
    id: string;
    userId: string;
    token: string;
    expiresAt: Date;
  };
  user: {
    id: string;
    email: string;
    emailVerified: boolean;
    name?: string;
    image?: string;
    createdAt: Date;
    updatedAt: Date;
  };
}

Custom Authorization

const mastraAuth = new MastraAuthBetterAuth({
  auth,
  authorizeUser: async ({ session, user }) => {
    // Check if email is verified
    if (!user.emailVerified) {
      return false;
    }

    // Check if session is expired
    if (new Date(session.expiresAt) < new Date()) {
      return false;
    }

    // Check user role (if you have roles plugin)
    // const hasRole = user.roles?.includes('admin');

    return true;
  },
});

Database Setup

better-auth automatically creates required tables:
const auth = betterAuth({
  database: {
    provider: 'postgresql', // or 'mysql', 'sqlite'
    url: process.env.DATABASE_URL!,
  },
});
Tables created:
  • user - User accounts
  • session - Active sessions
  • account - Social provider accounts
  • verification - Email verification tokens

Plugins

Two-Factor Authentication

import { twoFactor } from 'better-auth/plugins';

const auth = betterAuth({
  database: {
    provider: 'postgresql',
    url: process.env.DATABASE_URL!,
  },
  plugins: [twoFactor()],
});

Organization/Multi-Tenancy

import { organization } from 'better-auth/plugins';

const auth = betterAuth({
  database: {
    provider: 'postgresql',
    url: process.env.DATABASE_URL!,
  },
  plugins: [organization()],
});

Best Practices

Self-Hosted Control

You own the database and have full control over user data and authentication logic.

Enable Email Verification

Require email verification for better security:
requireEmailVerification: true

Use Environment Variables

Store OAuth secrets and database URLs in environment variables.

Session Management

Configure session expiration based on your security requirements.

Authentication Flow

Advantages

Self-Hosted

Complete control over your authentication system and user data

Open Source

Fully open-source with transparent implementation

Framework Agnostic

Works with any JavaScript framework or runtime

Extensible

Plugin system for adding custom functionality

Clerk

Managed authentication alternative

Supabase Auth

Open-source auth with database

better-auth Docs

Official better-auth documentation

better-auth GitHub

Open-source repository

Build docs developers (and LLMs) love