- name
- neon-auth-react
- description
- Sets up Neon Auth in React applications (Vite, CRA). Configures authentication adapters, creates auth client, and sets up UI components. Use when adding auth-only to React apps (no database needed).
- allowed-tools
- ["Bash","Write","Read","Edit","Glob","Grep"]
# Neon Auth for React
Help developers set up @neondatabase/auth (authentication only, no database) in React applications with Vite, Create React App, or similar bundlers.
## When to Use
Use this skill when:
- Setting up auth-only in React (no database needed)
- User already has a database solution
- User mentions "@neondatabase/auth" without "neon-js"
- User is NOT using Next.js (use `neon-auth-nextjs` skill for Next.js)
## Critical Rules
1. **Adapter Factory Pattern**: Always call adapters with `()` - they are factory functions
2. **React Adapter Import**: Use subpath `@neondatabase/auth/react/adapters`
3. **createAuthClient takes URL as first arg**: `createAuthClient(url, config)`
4. **CSS Import**: Choose ONE - either `/ui/css` OR `/ui/tailwind`, never both
## Setup
### 1. Install
```bash
npm install @neondatabase/auth
```
### 2. Create Client (`src/auth-client.ts`)
```typescript
import { createAuthClient } from '@neondatabase/auth';
import { BetterAuthReactAdapter } from '@neondatabase/auth/react/adapters';
export const authClient = createAuthClient(
import.meta.env.VITE_AUTH_URL,
{
adapter: BetterAuthReactAdapter(),
// allowAnonymous: true, // Enable for RLS access without login
}
);
```
### 3. Create Provider (`src/providers.tsx`)
```typescript
import { NeonAuthUIProvider } from '@neondatabase/auth/react/ui';
import { useNavigate } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { authClient } from './auth-client';
// Import CSS (choose one)
import '@neondatabase/auth/ui/css';
export function Providers({ children }: { children: React.ReactNode }) {
const navigate = useNavigate();
return (
<NeonAuthUIProvider
authClient={authClient}
navigate={navigate}
redirectTo="/dashboard"
Link={({ children, href }) => <Link to={href}>{children}</Link>}
>
{children}
</NeonAuthUIProvider>
);
}
```
### 4. Wrap App (`src/main.tsx`)
```typescript
import { BrowserRouter } from 'react-router-dom';
import { Providers } from './providers';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<BrowserRouter>
<Providers>
<App />
</Providers>
</BrowserRouter>
);
```
---
## CSS & Styling
### Import Options
**Without Tailwind** (pre-built CSS bundle ~47KB):
```css
/* In your main CSS file or import in provider */
@import '@neondatabase/auth/ui/css';
```
**With Tailwind CSS v4**:
```css
@import 'tailwindcss';
@import '@neondatabase/auth/ui/tailwind';
```
**IMPORTANT**: Never import both - causes duplicate styles.
### Dark Mode
The provider includes `next-themes` for dark mode. Control via `defaultTheme` prop:
```typescript
<NeonAuthUIProvider
authClient={authClient}
defaultTheme="system" // 'light' | 'dark' | 'system'
// ...
>
```
### Custom Theming
Override CSS variables in your stylesheet:
```css
:root {
--primary: oklch(0.7 0.15 250);
--primary-foreground: oklch(0.98 0 0);
--background: oklch(1 0 0);
--foreground: oklch(0.1 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.1 0 0);
--border: oklch(0.9 0 0);
--input: oklch(0.9 0 0);
--ring: oklch(0.7 0 0);
--radius: 0.5rem;
/* See theme.css for full list */
}
.dark {
--background: oklch(0.15 0 0);
--foreground: oklch(0.98 0 0);
/* Dark mode overrides */
}
```
---
## NeonAuthUIProvider Props
Full configuration options:
```typescript
<NeonAuthUIProvider
// Required
authClient={authClient}
// Navigation (required for React Router)
navigate={navigate} // Router's navigate function
Link={({href, children}) => <Link to={href}>{children}</Link>} // Router's Link component
redirectTo="/dashboard" // Where to redirect after auth
// Social/OAuth Providers
social={{
providers: ['google'],
}}
// Feature Flags
emailOTP={true} // Enable email OTP sign-in
emailVerification={true} // Require email verification
magicLink={false} // Magic link (disabled by default)
multiSession={false} // Multiple sessions (disabled)
// Credentials Configuration
credentials={{
forgotPassword: true, // Show forgot password link
}}
// Sign Up Fields
signUp={{
fields: ['name'], // Additional fields: 'name', 'username', etc.
}}
// Account Settings Fields
account={{
fields: ['image', 'name', 'company', 'age', 'newsletter'],
}}
// Avatar Configuration
avatar={{
size: 256,
extension: 'webp',
}}
// Organization Features
organization={{}} // Enable org features
// Dark Mode
defaultTheme="system" // 'light' | 'dark' | 'system'
// Custom Labels
localization={{
SIGN_IN: 'Welcome Back',
SIGN_IN_DESCRIPTION: 'Sign in to your account',
SIGN_UP: 'Create Account',
SIGN_UP_DESCRIPTION: 'Join us today',
FORGOT_PASSWORD: 'Forgot Password?',
OR_CONTINUE_WITH: 'or continue with',
// See better-auth-ui docs for full list
}}
>
{children}
</NeonAuthUIProvider>
```
---
## UI Components
### AuthView - Main Auth Interface
Handles sign-in, sign-up, forgot password, and callback routes:
```typescript
import { AuthView } from '@neondatabase/auth/react/ui';
// Route: /auth/:pathname
function AuthPage() {
const { pathname } = useParams(); // 'sign-in', 'sign-up', 'forgot-password', etc.
return <AuthView pathname={pathname} />;
}
```
**Supported pathnames**: `sign-in`, `sign-up`, `forgot-password`, `reset-password`, `callback`, `sign-out`
### Conditional Rendering
```typescript
import {
SignedIn,
SignedOut,
AuthLoading,
RedirectToSignIn
} from '@neondatabase/auth/react/ui';
function MyPage() {
return (
<>
{/* Show while checking auth state */}
<AuthLoading>
<LoadingSpinner />
</AuthLoading>
{/* Show only when authenticated */}
<SignedIn>
<Dashboard />
</SignedIn>
{/* Show only when NOT authenticated */}
<SignedOut>
<LandingPage />
</SignedOut>
{/* Redirect to sign-in if not authenticated */}
<RedirectToSignIn />
</>
);
}
```
### UserButton
Dropdown menu with user avatar, name, and sign-out:
```typescript
import { UserButton } from '@neondatabase/auth/react/ui';
function Header() {
return (
<header>
<nav>...</nav>
<UserButton />
</header>
);
}
```
### Account Management Components
```typescript
import {
AccountSettingsCards, // Profile info (avatar, name, email)
SecuritySettingsCards, // Security options (linked accounts)
SessionsCard, // Active sessions management
ChangePasswordCard, // Password change form
ChangeEmailCard, // Email change form
DeleteAccountCard, // Account deletion
ProvidersCard, // Linked OAuth providers
} from '@neondatabase/auth/react/ui';
function AccountPage() {
const { view } = useParams(); // 'settings', 'security', 'sessions'
return (
<>
<RedirectToSignIn />
<SignedIn>
{view === 'settings' && <AccountSettingsCards />}
{view === 'security' && (
<>
<ChangePasswordCard />
<SecuritySettingsCards />
</>
)}
{view === 'sessions' && <SessionsCard />}
</SignedIn>
</>
);
}
```
### Organization Components
```typescript
import {
OrganizationSwitcher, // Switch between orgs
OrganizationSettingsCards, // Org settings
OrganizationMembersCard, // Member management
AcceptInvitationCard, // Accept org invite
} from '@neondatabase/auth/react/ui';
```
---
## Adapter Options
### BetterAuthReactAdapter (Recommended for React)
Native Better Auth API with React hooks:
```typescript
import { BetterAuthReactAdapter } from '@neondatabase/auth/react/adapters';
const authClient = createAuthClient(url, {
adapter: BetterAuthReactAdapter(),
});
// Methods
await authClient.signIn.email({ email, password });
await authClient.signUp.email({ email, password, name });
await authClient.signIn.social({ provider: 'google', callbackURL: '/dashboard' });
await authClient.signOut();
const session = await authClient.getSession();
// React Hook
const { data, isPending, error } = authClient.useSession();
```
### SupabaseAuthAdapter (Supabase-compatible API)
For migrating from Supabase or familiar API:
```typescript
import { SupabaseAuthAdapter } from '@neondatabase/auth/vanilla/adapters';
const authClient = createAuthClient(url, {
adapter: SupabaseAuthAdapter(),
});
// Supabase-style methods
await authClient.signUp({ email, password, options: { data: { name } } });
await authClient.signInWithPassword({ email, password });
await authClient.signInWithOAuth({ provider: 'google', options: { redirectTo } });
await authClient.signOut();
const { data: session } = await authClient.getSession();
// Event listener
authClient.onAuthStateChange((event, session) => {
console.log(event); // 'SIGNED_IN', 'SIGNED_OUT', 'TOKEN_REFRESHED', 'USER_UPDATED'
});
```
### BetterAuthVanillaAdapter (Non-React)
For vanilla JS/TS without React hooks:
```typescript
import { BetterAuthVanillaAdapter } from '@neondatabase/auth/vanilla/adapters';
const authClient = createAuthClient(url, {
adapter: BetterAuthVanillaAdapter(),
});
// Same API as BetterAuthReactAdapter, but no useSession() hook
```
---
## Social/OAuth Providers
### Configuration
Enable providers in NeonAuthUIProvider:
```typescript
<NeonAuthUIProvider
social={{
providers: ['google'],
}}
>
```
### Programmatic OAuth Sign-In
```typescript
// BetterAuth API
await authClient.signIn.social({
provider: 'google',
callbackURL: '/dashboard',
scopes: ['email', 'profile'], // Optional
});
// Supabase API
await authClient.signInWithOAuth({
provider: 'google',
options: {
redirectTo: '/dashboard',
scopes: 'email profile',
},
});
```
### Supported Providers
`google`, `github`, `twitter`, `discord`, `apple`, `microsoft`, `facebook`, `linkedin`, `spotify`, `twitch`, `gitlab`, `bitbucket`
### OAuth in Iframes
OAuth automatically uses popup flow when running in iframes (due to X-Frame-Options restrictions). No configuration needed.
---
## Session Hook
Voir sur GitHub