Related docs
Repository
How to use
You can use these rules in two ways:
- Option 1: Copy from this page- With Cursor, save the rules to - .cursor/rules/neon-auth.mdcand they'll be automatically applied when working with matching files (- *.ts,- *.tsx).- For other AI tools, you can include these rules as context when chatting with your AI assistant - check your tool's documentation for the specific method (like using "Include file" or context commands). 
- Option 2: Clone from repository- If you prefer, you can clone or download the rules directly from our AI Rules repository. - Once added to your project, AI tools will automatically use these rules when working with Neon Auth code. You can also reference them explicitly in prompts. 
Rules
---
description: Use these rules to relate your database data with your Auth users information
globs: *.tsx, *.ts
alwaysApply: false
---
# Neon Auth guidelines
## The Problem Neon Auth Solves
Neon Auth integrates user authentication directly with your Neon Postgres database. Its primary purpose is to **eliminate the complexity of synchronizing user data** between your authentication provider and your application's database.
-   **Before Neon Auth:** Developers need to build and maintain custom sync logic, webhooks, and separate user tables to handle user creation, updates, and deletions. This is error-prone and adds overhead.
-   **With Neon Auth:** User data is automatically populated and updated in near real-time within a dedicated `neon_auth.users_sync` table in your database. This allows you to treat user profiles as regular database rows, ready for immediate use in SQL joins and application logic.
## The Two Halves of Neon Auth
Think of Neon Auth as a unified system with two main components:
1.  **The Authentication Layer (SDK):** This is for managing user sessions, sign-ins, sign-ups, and accessing user information in your application code (client and server components). It is powered by the Stack Auth SDK (`@stackframe/stack`).
2.  **The Database Layer (Data Sync):** This is the `neon_auth.users_sync` table within your Neon database. It serves as a near real-time, read-only replica of your user data, ready to be joined with your application's tables.
## Stack Auth Setup Guidelines
### Initial Setup
Ask the human developer to do the following steps:
- Enable Neon Auth: In the Neon project console, navigate to the Auth page and click Enable Neon Auth.
- Get Credentials: Go to the Configuration tab and copy the environment variables.
Steps which you can do after that:
- Run the installation wizard with:  
  `npx @stackframe/init-stack@latest --agent-mode --no-browser`
- Update the API keys in your `.env.local` file with the values from the Neon console:  
  - `NEXT_PUBLIC_STACK_PROJECT_ID`  
  - `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY`  
  - `STACK_SECRET_SERVER_KEY`
- Key files created/updated include:  
  - `app/handler/[...stack]/page.tsx` (default auth pages)  
  - `app/layout.tsx` (wrapped with StackProvider and StackTheme)  
  - `app/loading.tsx` (provides a Suspense fallback)  
  - `stack/server.tsx` (initializes your Stack server app)
  - `stack/client.tsx` (initializes your Stack client app)
### UI Components
- Use pre-built components from `@stackframe/stack` like `<UserButton />`, `<SignIn />`, and `<SignUp />` to quickly set up auth UI.
- You can also compose smaller pieces like `<OAuthButtonGroup />`, `<MagicLinkSignIn />`, and `<CredentialSignIn />` for custom flows.
- Example:
  
  ```tsx
  import { SignIn } from '@stackframe/stack';
  export default function Page() {
    return <SignIn />;
  }
  ```
### User Management
- In Client Components, use the `useUser()` hook to retrieve the current user (it returns `null` when not signed in).
- Update user details using `user.update({...})` and sign out via `user.signOut()`.
### Client Component Integration
- Client Components rely on hooks like `useUser()` and `useStackApp()`.
- Example:
  
  ```tsx
  "use client";
  import { useUser } from "@stackframe/stack";
  export function MyComponent() {
    const user = useUser();
    return <div>{user ? `Hello, ${user.displayName}` : "Not logged in"}</div>;
  }
  ```
### Server Component Integration
- For Server Components, use `stackServerApp.getUser()` from `stack/server.tsx` file.
- Example:
  
  ```tsx
  import { stackServerApp } from "@/stack/server";
  export default async function ServerComponent() {
    const user = await stackServerApp.getUser();
    return <div>{user ? `Hello, ${user.displayName}` : "Not logged in"}</div>;
  }
  ```
### Page Protection
- Protect pages by redirecting to Sign in page:
  - Using `useUser({ or: "redirect" })` in Client Components.
  - Using `await stackServerApp.getUser({ or: "redirect" })` in Server Components.
  - Implementing middleware that checks for a user and redirects to `/handler/sign-in` if not found.
- Example middleware:
  
  ```tsx
  export async function middleware(request: NextRequest) {
    const user = await stackServerApp.getUser();
    if (!user) {
      return NextResponse.redirect(new URL('/handler/sign-in', request.url));
    }
    return NextResponse.next();
  }
  export const config = { matcher: '/protected/:path*' };
  ```
## Stack Auth SDK Reference
The Stack Auth SDK provides several types and methods:
```tsx
type StackClientApp = {
  new(options): StackClientApp;
  getUser([options]): Promise<User>;
  useUser([options]): User;
  getProject(): Promise<Project>;
  useProject(): Project;
  signInWithOAuth(provider): void;
  signInWithCredential([options]): Promise<...>;
  signUpWithCredential([options]): Promise<...>;
  sendForgotPasswordEmail(email): Promise<...>;
  sendMagicLinkEmail(email): Promise<...>;
};
type StackServerApp =
  & StackClientApp
  & {
    new(options): StackServerApp;
    getUser([id][, options]): Promise<ServerUser | null>;
    useUser([id][, options]): ServerUser;
    listUsers([options]): Promise<ServerUser[]>;
    useUsers([options]): ServerUser[];
    createUser([options]): Promise<ServerUser>;
    getTeam(id): Promise<ServerTeam | null>;
    useTeam(id): ServerTeam;
    listTeams(): Promise<ServerTeam[]>;
    useTeams(): ServerTeam[];
    createTeam([options]): Promise<ServerTeam>;
  }
type CurrentUser = {
  id: string;
  displayName: string | null;
  primaryEmail: string | null;
  primaryEmailVerified: boolean;
  profileImageUrl: string | null;
  signedUpAt: Date;
  hasPassword: boolean;
  clientMetadata: Json;
  clientReadOnlyMetadata: Json;
  selectedTeam: Team | null;
  update(data): Promise<void>;
  updatePassword(data): Promise<void>;
  getAuthHeaders(): Promise<Record<string, string>>;
  getAuthJson(): Promise<{ accessToken: string | null }>;
  signOut([options]): Promise<void>;
  delete(): Promise<void>;
  getTeam(id): Promise<Team | null>;
  useTeam(id): Team | null;
  listTeams(): Promise<Team[]>;
  useTeams(): Team[];
  setSelectedTeam(team): Promise<void>;
  createTeam(data): Promise<Team>;
  leaveTeam(team): Promise<void>;
  getTeamProfile(team): Promise<EditableTeamMemberProfile>;
  useTeamProfile(team): EditableTeamMemberProfile;
  hasPermission(scope, permissionId): Promise<boolean>;
  getPermission(scope, permissionId[, options]): Promise<TeamPermission | null>;
  usePermission(scope, permissionId[, options]): TeamPermission | null;
  listPermissions(scope[, options]): Promise<TeamPermission[]>;
  usePermissions(scope[, options]): TeamPermission[];
  listContactChannels(): Promise<ContactChannel[]>;
  useContactChannels(): ContactChannel[];
};
```
## Stack Auth Best Practices
- Use the appropriate methods based on component type:
  - Use hook-based methods (`useXyz`) in Client Components
  - Use promise-based methods (`getXyz`) in Server Components
- Always protect sensitive routes using the provided mechanisms
- Use pre-built UI components whenever possible to ensure proper auth flow handling
## Neon Auth Database Integration
### Database Schema
Neon Auth creates and manages a schema in your database that stores user information:
- **Schema Name**: `neon_auth`
- **Primary Table**: `users_sync`
- **Table Structure**:
  - `raw_json` (JSONB, NOT NULL): Complete user data in JSON format
  - `id` (TEXT, NOT NULL, PRIMARY KEY): Unique user identifier
  - `name` (TEXT, NULLABLE): User's display name
  - `email` (TEXT, NULLABLE): User's email address
  - `created_at` (TIMESTAMP WITH TIME ZONE, NULLABLE): When the user was created
  - `updated_at` (TIMESTAMP WITH TIME ZONE, NULLABLE): When the user was last updated
  - `deleted_at` (TIMESTAMP WITH TIME ZONE, NULLABLE): When the user was deleted (if applicable)
- **Indexes**:
  - `users_sync_deleted_at_idx` on `deleted_at`: For quickly identifying deleted users
> NOTE: The table is automatically created and managed by Neon Auth. Do not manually create or modify it. This is provided for your reference only.
### Database Usage
#### Querying Active Users
The `deleted_at` column is used for soft deletes. Always include `WHERE deleted_at IS NULL` in your queries to ensure you only work with active user accounts.
```sql
SELECT * FROM neon_auth.users_sync WHERE deleted_at IS NULL;
```
#### Relating User Data with Application Tables
To join user data with your application tables:
```sql
SELECT 
  t.*,
  u.id AS user_id,
  u.name AS user_name,
  u.email AS user_email
FROM 
  public.todos t
LEFT JOIN 
  neon_auth.users_sync u ON t.owner = u.id
WHERE 
  u.deleted_at IS NULL
ORDER BY 
  t.id;
```
## Integration Flow
1. User authentication happens via Stack Auth UI components
2. User data is automatically synced to the `neon_auth.users_sync` table
3. Your application code accesses user information either through:
   - Stack Auth hooks/methods (in React components)
   - SQL queries to the `neon_auth.users_sync` table (for read only data operations)
## Best Practices for Integration
- **The `users_sync` Table is a Read-Only Replica**: User data is managed by the Neon Auth service. **NEVER** `INSERT`, `UPDATE`, or `DELETE` rows directly in the `neon_auth.users_sync` table. All user modifications must happen through the Authentication Layer SDK (e.g., `user.update({...})`, `user.delete()`). Direct database modifications will be overwritten and can break the sync process.
- **Use Foreign Keys Correctly**: You **SHOULD** create foreign key constraints from your application tables *to* the `neon_auth.users_sync(id)` column. This maintains referential integrity. Do **NOT** attempt to add foreign keys *from* the `users_sync` table to your own tables.
  ```sql
  -- CORRECT: Your table references the Neon Auth table.
  CREATE TABLE posts (
      id SERIAL PRIMARY KEY,
      content TEXT,
      author_id TEXT NOT NULL REFERENCES neon_auth.users_sync(id) ON DELETE CASCADE
  );
  -- INCORRECT: Do not try to alter the Neon Auth table. Will break entire Neon Auth system.
  -- ALTER TABLE neon_auth.users_sync ADD CONSTRAINT ...
  ```
## Example: Custom Profile Page with Database Integration
### Frontend Component
```tsx
'use client';
import { useUser, useStackApp, UserButton } from '@stackframe/stack';
export default function ProfilePage() {
  const user = useUser({ or: "redirect" });
  const app = useStackApp();
  return (
    <div>
      <UserButton />
      <h1>Welcome, {user.displayName || "User"}</h1>
      <p>Email: {user.primaryEmail}</p>
      <button onClick={() => user.signOut()}>Sign Out</button>
    </div>
  );
}
```
### Database Query for User's Content
```sql
-- Get all todos for the currently logged in user
SELECT 
  t.*
FROM 
  public.todos t
LEFT JOIN 
  neon_auth.users_sync u ON t.owner = u.id
WHERE 
  u.id = $current_user_id
  AND u.deleted_at IS NULL
ORDER BY 
  t.created_at DESC;
```