ArchitectureBetter Auth Plugins Analysis & Recommendations

Better Auth Plugins Analysis & Recommendations

Most of the "Recommended"/"Under Consideration" verdicts below are now out of date — organization(), haveIBeenPwned(), lastLoginMethod(), oneTimeToken(),...

Better Auth Plugins Analysis & Recommendations

Superseded (2026-07-10)

Most of the "Recommended"/"Under Consideration" verdicts below are now out of date — organization(), haveIBeenPwned(), lastLoginMethod(), oneTimeToken(), jwt(), deviceAuthorization(), and (feature-flagged) apiKey(), mcp(), oidcProvider(), sso(), scim(), passkey(), captcha() are now wired into worker/lib/auth.ts. Treat the module-level JSDoc at the top of worker/lib/auth.ts as the single source of truth for current plugin status — it can't drift out of sync with the code the way this analysis document can. The sections below are kept for their original cost/benefit/ZTA reasoning, but their Status/Recommendation lines predate the current implementation. See each plugin's isXEnabled()/buildXOptions() JSDoc in auth.ts for the up-to-date rationale, including why apiKey() is a deliberately separate system from the custom implementation described below, and why stripe() is installed but never mounted.

For task-oriented "how do I enable X" instructions — env vars, endpoints exposed, schema, and rollout gotchas — see the Plugin Extension Pack guide.

Executive Summary

This document provides a comprehensive analysis of Better Auth plugins for the bloqr-backend project, evaluating their implementation status, benefits, and recommendations for adoption.

Status at a Glance (superseded — see callout above):
  • ✅ 4 plugins already active: bearer, twoFactor, multiSession, admin
  • 🔄 1 plugin recommended for implementation: organization
  • 📋 6 plugins under consideration: apiKey, magicLink, username, jwt, hibp, captcha, openAPI

Currently Implemented Plugins

✅ bearer() Plugin

Status: Active (auth.ts:175)

Purpose: Enables API authentication via Authorization: Bearer <token> header instead of browser cookies.

Endpoints Auto-Exposed:
  • Session token endpoint returns session.token in responses
Usage:
  • Frontend: getToken() method retrieves session token for API calls
  • Critical for API-first architecture
  • Enables Postman/curl access with tokens
Benefits:
  • ✅ Essential for our API-centric architecture
  • ✅ Already integrated with middleware/auth.ts
  • ✅ Frontend service has getToken() method

Database Schema: Uses existing Session.token field

ZTA Compliance: ✅ Fully compliant - tokens verified via Better Auth


✅ twoFactor() Plugin

Status: Active (auth.ts:180)

Purpose: TOTP-based two-factor authentication for enhanced account security.

Configuration:
twoFactor({
    issuer: 'bloqr-backend'
})
Endpoints Auto-Exposed:
  • POST /api/auth/two-factor/enable - Generate TOTP secret + QR URI
  • POST /api/auth/two-factor/verify - Verify TOTP code (enables 2FA)
  • POST /api/auth/two-factor/disable - Remove 2FA
Database Schema:
model TwoFactor {
  id          String @id @default(uuid())
  userId      String @unique
  secret      String  // Encrypted TOTP secret
  backupCodes String  // JSON array
}

// User model field:
twoFactorEnabled Boolean @default(false)
Frontend Integration:
enableTwoFactor(password)     // Get TOTP URI for QR
verifyTwoFactor(code)         // Verify code
disableTwoFactor(password)    // Disable 2FA
Benefits:
  • ✅ Critical security feature for list/rule managers
  • ✅ Easy user adoption (QR code scanning)
  • ✅ Backup codes for recovery
  • ✅ No additional dependencies

Recommendation: ✅ Keep active - essential security feature


✅ multiSession() Plugin

Status: Active (auth.ts:187)

Purpose: Multiple active sessions per user across devices.

Endpoints Auto-Exposed:
  • GET /api/auth/list-sessions - List all active sessions
  • POST /api/auth/revoke-session - Revoke specific session
  • POST /api/auth/revoke-other-sessions - Revoke all except current

Database Schema: Uses existing Session model with tracking

Frontend Integration:
listSessions()              // Get all active sessions
revokeSession(token)        // Logout specific session
revokeOtherSessions()       // Logout all other devices
Benefits:
  • ✅ Users can manage active devices
  • ✅ Security: logout stolen/lost devices
  • ✅ UX: see where you're logged in

Recommendation: ✅ Keep active - valuable security & UX feature


✅ admin() Plugin

Status: Active (auth.ts:195)

Purpose: Built-in admin user and role management.

Endpoints Auto-Exposed:
  • GET /api/auth/admin/list-users - List all users
  • POST /api/auth/admin/set-role - Change user role
  • POST /api/auth/admin/ban-user - Ban a user
  • POST /api/auth/admin/unban-user - Unban a user
  • POST /api/auth/admin/impersonate-user - Impersonate user
  • POST /api/auth/admin/revoke-user-sessions - Revoke all sessions
Database Schema:
model User {
  role       String    @default("user")
  banned     Boolean   @default(false)
  banReason  String?
  banExpires DateTime?
}

Integration: Already integrated with custom admin routes at /admin/users/*

Benefits:
  • ✅ Robust admin UI and roles
  • ✅ Simplifies user management
  • ✅ Ban management built-in

Recommendation: ✅ Keep active - critical for administration


🔄 organization() Plugin

Status: Not implemented (commented at auth.ts:198)

Purpose: Multi-tenancy support - enables teams/organizations with member roles and permissions.

Use Cases for Our Project:
  • Team-based compilation workflows
  • Shared filter list ownership
  • Organization-level API keys
  • Multi-team list moderation
Endpoints Auto-Exposed:
  • POST /api/auth/organization/create - Create organization
  • POST /api/auth/organization/invite-member - Invite user to org
  • POST /api/auth/organization/remove-member - Remove member
  • POST /api/auth/organization/update-member-role - Change member role
  • GET /api/auth/organization/list-organizations - List user's orgs
  • GET /api/auth/organization/get-full-organization - Get org details
Database Schema Required:
model Organization {
  id          String   @id @default(uuid())
  name        String
  slug        String   @unique
  logo        String?
  createdAt   DateTime @default(now())
  metadata    Json?

  members     Member[]
}

model Member {
  id             String       @id @default(uuid())
  organizationId String
  userId         String
  role           String       // owner | admin | member
  createdAt      DateTime     @default(now())

  organization   Organization @relation(fields: [organizationId], references: [id])
  user           User         @relation(fields: [userId], references: [id])

  @@unique([organizationId, userId])
}
Configuration:
organization({
  allowUserToCreateOrganization: true,  // All users can create orgs
  organizationLimit: 3,                  // Max 3 orgs per user
})
Benefits:
  • ✅ Native multi-tenancy support
  • ✅ Built-in role system (owner/admin/member)
  • ✅ Invitation workflow included
  • ✅ Scales with business needs

Implementation Complexity: Medium

  • Database migration required
  • Frontend service updates needed
  • Optional: Update FilterSource.ownerUserId to support org ownership

Recommendation: ✅ IMPLEMENT - High value for collaborative use cases


Plugins Under Consideration

📋 apiKey() Plugin (Custom vs. Built-in)

Status: Custom implementation active at worker/handlers/api-keys.ts

Better Auth Plugin: Provides standardized API key management at /api/auth/admin/api-keys/*

Current Custom Implementation:
// worker/routes/api-keys.routes.ts
POST   /api/keys         // Create key
GET    /api/keys         // List keys
DELETE /api/keys/:id     // Revoke key
PATCH  /api/keys/:id     // Update key
Database Schema:
model ApiKey {
  id                 String    @id @default(uuid())
  userId             String
  keyHash            String    @unique
  keyPrefix          String    // Display prefix
  name               String
  scopes             String[]  @default(["compile"])
  rateLimitPerMinute Int       @default(60)
  lastUsedAt         DateTime?
  expiresAt          DateTime?
  revokedAt          DateTime?
}
Custom Features:
  • ✅ Per-key scopes (compile, admin, etc.)
  • ✅ Per-key rate limits
  • ✅ Expiration management
  • ✅ Last used tracking
Better Auth apiKey() Plugin Features:
  • Standard API key CRUD
  • Simpler implementation
  • Less customization
Analysis:
  • Our custom implementation has richer features (scopes, per-key limits)
  • Better Auth plugin would simplify maintenance
  • Migration would require schema changes

Recommendation: ⚠️ KEEP CUSTOM - Our implementation is more feature-rich

  • Optional: Add Better Auth plugin for secondary API key type (read-only tokens)

Status: Not implemented

Purpose: Passwordless login via email links.

Use Case:
  • Simplify login UX
  • Reduce password fatigue
  • Good for occasional users
Implementation:
magicLink({
  sendMagicLink: async ({ email, url }) => {
    // Send email with magic link
    // Requires email service integration
  }
})
Pros:
  • ✅ Better UX for casual users
  • ✅ No passwords to forget
  • ✅ Built-in token management
Cons:
  • ❌ Requires email service (Resend, Postmark, etc.)
  • ❌ Additional cost for email sending
  • ❌ May confuse users with multiple login methods

Recommendation: 🔄 DEFER - Implement when email service is ready

  • Priority: Medium
  • Complexity: Low (once email service is available)

📋 username() Plugin

Status: Not implemented

Purpose: Username-based authentication instead of email-only.

Use Case:
  • Allow users to choose display names
  • Social/gaming-style login

Current: Users have displayName field but login is email-only

Pros:
  • ✅ More casual/friendly UX
  • ✅ Username-based profiles
Cons:
  • ❌ Requires unique username constraints
  • ❌ Added complexity (username + email + password)
  • ❌ Username availability checks needed

Recommendation: ⏸️ LOW PRIORITY - Email auth sufficient for now

  • Revisit if community/social features are added

📋 jwt() Plugin

Status: Not needed (bearer plugin sufficient)

Purpose: Stateless JWT tokens for API authentication.

Current Solution: bearer() plugin already provides token-based auth

Analysis:
  • bearer() uses session tokens (validated via database)

  • jwt() uses stateless tokens (signed, no DB lookup)

  • Trade-offs:

    • bearer: Can revoke immediately (session deletion)
    • jwt: Faster (no DB lookup), but can't revoke until expiry

Recommendation: ❌ NOT NEEDED - bearer() plugin is sufficient

  • Our ZTA architecture requires database-validated tokens
  • Immediate revocation is critical for security

📋 hibp() Plugin (Have I Been Pwned)

Status: Not implemented

Purpose: Check passwords against breach databases during sign-up/password change.

Implementation:
import { passkey } from "better-auth/plugins"

hibp({
  // Checks password against HIBP API on sign-up/change
})
Pros:
  • ✅ Prevents known-breached passwords
  • ✅ Improves account security
  • ✅ Free API (k-anonymity)
Cons:
  • ❌ External API dependency (haveibeenpwned.com)
  • ❌ Slight sign-up delay (~200ms)
  • ❌ May confuse users if password rejected

Recommendation: 🔄 CONSIDER - Valuable security feature

  • Priority: Low-Medium
  • Complexity: Very Low (drop-in plugin)
  • Implement after core features stabilized

📋 captcha() Plugin

Status: Custom Turnstile implementation active

Purpose: Bot protection for sign-up/login.

Current Implementation:
  • Cloudflare Turnstile integrated at sign-up/login
  • Middleware: worker/middleware/turnstile.ts
  • Frontend: Turnstile component in sign-up/sign-in forms
Better Auth captcha() Plugin:
  • Generic captcha integration
  • Supports reCAPTCHA, hCaptcha, Turnstile

Recommendation: ❌ NOT NEEDED - Custom Turnstile integration sufficient

  • Our implementation is well-integrated
  • Better Auth plugin adds unnecessary abstraction

📋 openAPI() Plugin

Status: Partial - Hono OpenAPI active

Purpose: Auto-generate OpenAPI docs for auth endpoints.

Current State:
  • Hono OpenAPI integration at /api/openapi.json (hono-app.ts:41)
  • Documents custom routes
  • Better Auth endpoints not documented
Better Auth openAPI() Plugin:
import { openAPI } from "better-auth/plugins"

openAPI()  // Exposes GET /api/auth/.well-known/openapi.json
Pros:
  • ✅ Documents all auth endpoints
  • ✅ Enables Postman import
  • ✅ API exploration
Cons:
  • ❌ Separate OpenAPI spec (/api/auth/.well-known/openapi.json)
  • ❌ Need to merge with Hono OpenAPI spec

Recommendation: 🔄 CONSIDER - Good for developer experience

  • Priority: Low
  • Complexity: Low
  • Implement when consolidating API documentation

Implementation Priority

Phase 1 (Immediate - This PR)

  1. ✅ Document all plugins (this document)

  2. 🔄 Implement organization() plugin

    • Add database schema
    • Run migration
    • Update frontend service
    • Add organization management UI (optional)

Phase 2 (Near-term)

  1. 🔄 Evaluate magicLink() plugin

    • Requires email service decision
    • Implement once email provider chosen
  2. 🔄 Consider hibp() plugin

    • Low complexity, high security value
    • Implement after organization plugin stabilized

Phase 3 (Future)

  1. 🔄 Evaluate openAPI() plugin

    • Improve developer experience
    • Consolidate API documentation
  2. ⏸️ Defer username() plugin

    • Revisit if community features added
  3. ❌ Skip jwt() and captcha() plugins

    • Already covered by bearer() and custom Turnstile

ZTA Compliance Notes

All Better Auth endpoints must comply with Zero Trust Architecture:

Endpoint Security Requirements

  • Edge Layer: WAF/API Shield in sync with OpenAPI spec, CORS allowlist
  • Worker Layer: Auth chain before business logic, rate limiting on all endpoints
  • Validation: Zod schemas at trust boundaries
  • Telemetry: Security events for auth failures

Plugin-Specific ZTA

  • organization(): Membership checks before org operations
  • magicLink(): Token expiry validation (default: 5 minutes)
  • hibp(): No PII sent to external API (k-anonymity)

Database Migration Strategy

For organization() Plugin

Step 1: Generate schema changes

# Add Organization and Member models to schema.prisma
deno task db:migrate

Step 2: Apply migration

deno task db:migrate:deploy

Step 3: Verify migration

-- Check tables created
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public' AND table_name IN ('organizations', 'members');

Rollback Plan: Keep migration reversible

-- Rollback SQL (if needed)
DROP TABLE IF EXISTS members;
DROP TABLE IF EXISTS organizations;

Frontend Integration Guide

Adding Organization Support

Step 1: Extend BetterAuthService

// frontend/src/app/services/better-auth.service.ts

async createOrganization(name: string, slug: string): Promise<void> {
  const res = await fetch(`${this.apiBaseUrl}/auth/organization/create`, {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name, slug })
  });
  if (!res.ok) throw new Error('Failed to create organization');
}

async listOrganizations(): Promise<Organization[]> {
  const res = await fetch(`${this.apiBaseUrl}/auth/organization/list-organizations`, {
    credentials: 'include'
  });
  if (!res.ok) throw new Error('Failed to list organizations');
  return await res.json();
}
Step 2: Create OrganizationService (optional)
// frontend/src/app/services/organization.service.ts

@Injectable({ providedIn: 'root' })
export class OrganizationService {
  private readonly authService = inject(BetterAuthService);

  async inviteMember(orgId: string, email: string, role: string) { ... }
  async removeMember(orgId: string, userId: string) { ... }
  async updateMemberRole(orgId: string, userId: string, role: string) { ... }
}

Testing Strategy

Unit Tests

  • Test organization CRUD operations
  • Test member invitation workflow
  • Test role permission checks

Integration Tests

  • Test multi-user organization scenarios
  • Test organization-scoped API keys (if implemented)
  • Test organization ownership transfer

E2E Tests

  • Test organization creation flow
  • Test member invitation acceptance
  • Test organization switching in UI

Security Considerations

organization() Plugin

  • ✅ Role-based access control (owner/admin/member)
  • ✅ Unique org slugs prevent enumeration
  • ⚠️ Verify membership before org operations
  • ⚠️ Rate-limit org creation (prevent spam)

API Key Management

  • ✅ Keep custom implementation with scopes
  • ✅ Maintain per-key rate limits
  • ✅ Log API key usage for audit

Cost Analysis

Infrastructure Costs

  • organization() plugin:
    • Database: +2 tables, minimal storage impact
    • Worker CPU: Negligible (membership lookups cached)
  • magicLink() plugin:
    • Email service: ~$0.001 per email (Resend/Postmark)
    • Volume: Depends on user sign-ups
  • hibp() plugin:
    • External API: Free (Cloudflare partnership)
    • Latency: +200ms on password operations

Maintenance Implications

Better Auth Updates

  • Current version: ^1.5.6
  • Update frequency: Monthly releases
  • Breaking changes: Rare (semantic versioning)

Plugin Maintenance

  • Built-in plugins: Updated with Better Auth core
  • Custom plugins: Maintained separately
  • Migration effort: Low (plugins are modular)

Conclusion

Summary of Recommendations:

✅ Keep Active (4):

  • bearer, twoFactor, multiSession, admin

🔄 Implement Now (1):

  • organization (multi-tenancy support)

📋 Consider Future (3):

  • magicLink (when email service ready)
  • hibp (low-hanging security fruit)
  • openAPI (developer experience)

⏸️ Defer (1):

  • username (not critical)

❌ Skip (2):

  • jwt (bearer sufficient)
  • captcha (Turnstile active)
Next Steps:
  1. Implement organization() plugin
  2. Generate and apply database migration
  3. Update frontend service
  4. Test multi-tenancy workflows
  5. Document usage for team administrators

References