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.mdc
and 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;
```