better-auth-electron

Better Auth integration for Electron desktop apps with secure IPC, context isolation, and encrypted session storage

16 stars

Best use case

better-auth-electron is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Better Auth integration for Electron desktop apps with secure IPC, context isolation, and encrypted session storage

Teams using better-auth-electron should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/better-auth-electron/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/better-auth-electron/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/better-auth-electron/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How better-auth-electron Compares

Feature / Agentbetter-auth-electronStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Better Auth integration for Electron desktop apps with secure IPC, context isolation, and encrypted session storage

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Better Auth - Electron Desktop Integration

**Better Auth** works seamlessly with Electron using the React client in the renderer process with secure IPC patterns.

## AI Tooling

**IMPORTANT**: Before implementing Better Auth in Electron, consult:

- **AI Documentation**: `https://better-auth.com/llms.txt`
- **MCP Server**: `https://mcp.chonkie.ai/better-auth/better-auth-builder/mcp`

Use Context7 to look up Better Auth patterns:

```
get_library_docs({ libraryName: "better-auth", topic: "react client" })
get_library_docs({ libraryName: "better-auth", topic: "session management" })
```

---

## Installation

```bash
# Install Better Auth and electron-store for session persistence
npm install better-auth electron-store
```

## Architecture

```
┌─────────────────────────────────────────────────────┐
│                 Main Process                         │
│  - Session validation via IPC                        │
│  - Secure token storage (electron-store)             │
│  - Auth state management                             │
└───────────────────┬─────────────────────────────────┘
                    │ IPC (contextBridge)
┌───────────────────▼─────────────────────────────────┐
│              Preload Script                          │
│  - Expose safe auth APIs to renderer                 │
│  - No direct Node.js access                          │
└───────────────────┬─────────────────────────────────┘
                    │
┌───────────────────▼─────────────────────────────────┐
│              Renderer Process                        │
│  - Better Auth React client                          │
│  - UI components (React/shadcn)                      │
│  - Uses window.authApi from preload                  │
└─────────────────────────────────────────────────────┘
```

---

## Backend Configuration

Your Electron app needs a Better Auth backend (can be local or remote):

**Backend (`server/auth.ts`):**

```typescript
import { betterAuth } from "better-auth"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import { db } from "./db"

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "sqlite" }),
  emailAndPassword: {
    enabled: true,
  },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
  // Trust Electron app origin
  trustedOrigins: [
    "app://.",          // Electron custom protocol
    "file://",          // File protocol
    "http://localhost", // Dev server
  ],
})
```

---

## Main Process

**`main.ts`:**

```typescript
import { app, BrowserWindow, ipcMain, shell } from "electron"
import Store from "electron-store"
import path from "path"

// Secure persistent storage for auth state
const store = new Store({
  name: "auth",
  encryptionKey: "your-encryption-key",  // Use secure key management
})

function createWindow() {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
      contextIsolation: true,   // REQUIRED - security
      nodeIntegration: false,   // REQUIRED - security
      sandbox: true,            // RECOMMENDED
    },
  })

  // Open OAuth callbacks in external browser
  win.webContents.setWindowOpenHandler(({ url }) => {
    if (url.startsWith("http")) {
      shell.openExternal(url)
      return { action: "deny" }
    }
    return { action: "allow" }
  })

  win.loadFile("index.html")
}

// IPC handlers for auth operations
ipcMain.handle("auth:get-stored-session", async () => {
  return store.get("session", null)
})

ipcMain.handle("auth:store-session", async (_, session) => {
  store.set("session", session)
})

ipcMain.handle("auth:clear-session", async () => {
  store.delete("session")
})

ipcMain.handle("auth:get-api-url", async () => {
  return process.env.AUTH_API_URL || "http://localhost:3000"
})

app.whenReady().then(createWindow)
```

---

## Preload Script

**`preload.ts`:**

```typescript
import { contextBridge, ipcRenderer } from "electron"

// Expose secure auth API to renderer
contextBridge.exposeInMainWorld("authApi", {
  // Session persistence
  getStoredSession: () => ipcRenderer.invoke("auth:get-stored-session"),
  storeSession: (session: unknown) => ipcRenderer.invoke("auth:store-session", session),
  clearSession: () => ipcRenderer.invoke("auth:clear-session"),
  
  // Config
  getApiUrl: () => ipcRenderer.invoke("auth:get-api-url"),
  
  // Events
  onAuthStateChange: (callback: (session: unknown) => void) => {
    ipcRenderer.on("auth:state-changed", (_, session) => callback(session))
  },
})

// Type declarations for renderer
declare global {
  interface Window {
    authApi: {
      getStoredSession: () => Promise<unknown>
      storeSession: (session: unknown) => Promise<void>
      clearSession: () => Promise<void>
      getApiUrl: () => Promise<string>
      onAuthStateChange: (callback: (session: unknown) => void) => void
    }
  }
}
```

---

## Renderer Process (React)

**Auth Client (`src/lib/auth-client.ts`):**

```typescript
import { createAuthClient } from "better-auth/react"

// Get API URL from main process
const getAuthClient = async () => {
  const baseURL = await window.authApi.getApiUrl()
  
  return createAuthClient({
    baseURL,
    // Custom fetch to handle Electron environment
    fetchOptions: {
      credentials: "include",
    },
  })
}

// Export singleton
let authClientPromise: ReturnType<typeof getAuthClient> | null = null

export const getClient = () => {
  if (!authClientPromise) {
    authClientPromise = getAuthClient()
  }
  return authClientPromise
}

// Hook for components
export function useAuthClient() {
  const [client, setClient] = useState<Awaited<ReturnType<typeof getAuthClient>> | null>(null)
  
  useEffect(() => {
    getClient().then(setClient)
  }, [])
  
  return client
}
```

**Simplified Client (if API URL is static):**

```typescript
import { createAuthClient } from "better-auth/react"

export const authClient = createAuthClient({
  baseURL: "http://localhost:3000",  // Your auth server
})

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

---

## Sign In Component

```typescript
import { useState } from "react"
import { authClient } from "@/lib/auth-client"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"

export function SignIn() {
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const handleSignIn = async (e: React.FormEvent) => {
    e.preventDefault()
    setLoading(true)
    setError(null)

    const { data, error } = await authClient.signIn.email({
      email,
      password,
    })

    if (error) {
      setError(error.message)
    } else if (data?.session) {
      // Persist session to main process store
      await window.authApi.storeSession(data.session)
    }
    
    setLoading(false)
  }

  return (
    <Card className="w-full max-w-md mx-auto">
      <CardHeader>
        <CardTitle>Sign In</CardTitle>
      </CardHeader>
      <CardContent>
        <form onSubmit={handleSignIn} className="space-y-4">
          <Input
            type="email"
            placeholder="Email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            required
          />
          <Input
            type="password"
            placeholder="Password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            required
          />
          {error && <p className="text-sm text-destructive">{error}</p>}
          <Button type="submit" className="w-full" disabled={loading}>
            {loading ? "Signing in..." : "Sign In"}
          </Button>
        </form>
      </CardContent>
    </Card>
  )
}
```

---

## Session Management

```typescript
import { useEffect } from "react"
import { authClient } from "@/lib/auth-client"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function UserMenu() {
  const { data: session, isPending } = authClient.useSession()

  // Sync session changes to main process
  useEffect(() => {
    if (session) {
      window.authApi.storeSession(session)
    }
  }, [session])

  const handleSignOut = async () => {
    await authClient.signOut()
    await window.authApi.clearSession()
  }

  if (isPending) {
    return <div className="h-8 w-8 animate-pulse rounded-full bg-muted" />
  }

  if (!session) {
    return <Button variant="outline">Sign In</Button>
  }

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="ghost" className="relative h-8 w-8 rounded-full">
          <Avatar className="h-8 w-8">
            <AvatarImage src={session.user.image || ""} />
            <AvatarFallback>
              {session.user.name?.[0]?.toUpperCase()}
            </AvatarFallback>
          </Avatar>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem className="font-medium">
          {session.user.name}
        </DropdownMenuItem>
        <DropdownMenuItem className="text-muted-foreground">
          {session.user.email}
        </DropdownMenuItem>
        <DropdownMenuItem onClick={handleSignOut}>
          Sign Out
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

---

## Restore Session on App Launch

```typescript
// App.tsx - Restore session when app starts
import { useEffect, useState } from "react"
import { authClient } from "@/lib/auth-client"

export function App() {
  const [initialized, setInitialized] = useState(false)

  useEffect(() => {
    async function restoreSession() {
      // Check for stored session from previous launch
      const storedSession = await window.authApi.getStoredSession()
      
      if (storedSession) {
        // Validate session with server
        const { data: currentSession } = await authClient.getSession()
        
        if (!currentSession) {
          // Session expired, clear stored data
          await window.authApi.clearSession()
        }
      }
      
      setInitialized(true)
    }
    
    restoreSession()
  }, [])

  if (!initialized) {
    return <div>Loading...</div>
  }

  return <MainApp />
}
```

---

## OAuth in Electron

For OAuth flows, open the auth URL in the system browser and handle the callback:

```typescript
import { shell } from "electron"  // Main process only

// In main process - handle OAuth callback
app.setAsDefaultProtocolClient("myapp")  // Register custom protocol

app.on("open-url", (event, url) => {
  // Handle OAuth callback: myapp://auth/callback?code=...
  if (url.includes("/auth/callback")) {
    mainWindow.webContents.send("auth:oauth-callback", url)
  }
})
```

---

## Security Best Practices

1. **Always use contextIsolation** - Never expose Node.js to renderer
2. **Encrypt stored sessions** - Use electron-store with encryption
3. **Validate sessions on startup** - Check with server before trusting local state
4. **Handle OAuth via system browser** - More secure than in-app browser
5. **Use sandbox mode** - Additional security layer
6. **Clear sensitive data on sign out** - Both in-memory and persistent storage

**Documentation**: https://better-auth.com/docs

Related Skills

better-auth

16
from diegosouzapw/awesome-omni-skill

The ultimate authentication and authorization skill. Implement login, signin, signup, registration, OAuth, 2FA, MFA, passkeys, and user session management. Secure your application with RBAC and access control.

better-auth-specialist

16
from diegosouzapw/awesome-omni-skill

Expert implementation of user authentication and authorization using Better Auth library for Next.js 15+/React 18+ frontends and Node.js/FastAPI backends with SQL and NoSQL databases. Use when implementing authentication systems, user login/signup, session management, protected routes, role-based access control (RBAC), OAuth integration, or any auth-related tasks including email/password authentication, JWT tokens, permissions, and user management.

better-auth-patterns

16
from diegosouzapw/awesome-omni-skill

Better Auth authentication patterns for TypeScript applications. Use when implementing authentication with Better Auth, configuring OAuth providers, setting up session management, integrating with Next.js/Astro/Hono/Express/TanStack Start, or configuring Drizzle/Prisma adapters.

better-auth-best-practices

16
from diegosouzapw/awesome-omni-skill

Skill for integrating Better Auth - the comprehensive TypeScript authentication framework.

authoring-excalidraw-files

16
from diegosouzapw/awesome-omni-skill

Generate architecture diagrams as .excalidraw files. Use when the user asks to create architecture diagrams, system diagrams, visualize codebase structure, infrastructure diagrams, or generate excalidraw files.

authentication

16
from diegosouzapw/awesome-omni-skill

Auth flows, session management, OAuth integration, domain-restricted access, and role-based access control for TopNetworks properties. Primary implementation is Better Auth 1.x with Google OAuth in route-genius. Use when implementing login, session checks, protected routes, or any access control logic.

auth0-quickstart

16
from diegosouzapw/awesome-omni-skill

Use when starting Auth0 integration in any framework - detects your stack (React, Next.js, Vue, Angular, Express, Fastify, React Native) and routes to correct SDK setup workflow

auth0-nextjs

16
from diegosouzapw/awesome-omni-skill

Use when adding authentication to Next.js applications with both server and client-side auth - supports App Router and Pages Router with @auth0/nextjs-auth0 SDK

auth0-fastify

16
from diegosouzapw/awesome-omni-skill

Use when adding authentication to Fastify server-rendered web applications with session management - integrates @auth0/auth0-fastify for high-performance web apps

auth0-express

16
from diegosouzapw/awesome-omni-skill

Use when adding authentication to Express.js server-rendered web applications with session management - integrates express-openid-connect for traditional web apps

auth-web-cloudbase

16
from diegosouzapw/awesome-omni-skill

Complete guide for CloudBase Auth v2 using Web SDK (@cloudbase/js-sdk@2.x) - all login flows, user management, captcha handling, and best practices in one file.

auth-tool-cloudbase

16
from diegosouzapw/awesome-omni-skill

Use CloudBase Auth tool to configure and manage authentication providers for web applications - enable/disable login methods (SMS, Email, WeChat Open Platform, Google, Anonymous, Username/password, OAuth, SAML, CAS, Dingding, etc.) and configure provider settings via MCP tools.