The short version
NextAuth (now Auth.js) creates 4 tables in your database: users, accounts, sessions, and verification_tokens. The users and accounts tables have a one-to-one relationship via accounts.user_id. Sessions link to users via sessions.user_id. Verification tokens are short-lived and self-cleaning.
The 4 tables
users
| Column | Type | What it means |
|---|---|---|
id |
text / UUID | Primary key. Generated by NextAuth. |
name |
text | Display name from the OAuth provider (Google, GitHub, etc.) |
email |
text | User's email. May be null if the provider doesn't share it. |
email_verified |
timestamp | When the email was verified. Null if never verified. |
image |
text | Profile picture URL from the provider. |
created_at |
timestamp | When the user first signed in. |
updated_at |
timestamp | Last profile sync from the provider. |
accounts
This table links a user to an OAuth provider. One user can have multiple accounts (e.g., Google + GitHub).
| Column | Type | What it means |
|---|---|---|
id |
text / UUID | Primary key. |
user_id |
text | Foreign key → users.id. |
type |
text | Always "oauth" or "oidc". |
provider |
text |
"google", "github", "discord", etc. |
provider_account_id |
text | The provider's unique ID for this user. |
refresh_token |
text | OAuth refresh token (encrypted in production). |
access_token |
text | OAuth access token (encrypted in production). |
expires_at |
integer | When the access token expires (Unix timestamp). |
token_type |
text | Usually "Bearer". |
scope |
text | Permissions granted by the provider. |
id_token |
text | OIDC ID token (if using OIDC). |
session_state |
text | Provider-specific session state. |
sessions
Active sessions for each user. NextAuth creates a new row here on every sign-in.
| Column | Type | What it means |
|---|---|---|
id |
text / UUID | Primary key. |
session_token |
text | The session token stored in the user's cookie. |
user_id |
text | Foreign key → users.id. |
expires |
timestamp | When this session expires. |
verification_tokens
Short-lived tokens for email verification, password reset, etc. Self-cleaning old tokens are deleted automatically.
| Column | Type | What it means |
|---|---|---|
identifier |
text | Email or user ID the token is for. |
token |
text | The actual token value. |
expires |
timestamp | When this token expires. |
How they connect
users ──1──1── accounts
│
1
│
∞
sessions
users ──1──∞── verification_tokens (via identifier)
- One user → one or more accounts (Google, GitHub, etc.)
- One user → many sessions (different devices/browsers)
- Verification tokens are temporary and don't have a foreign key
What to change
-
Add a
rolecolumn tousersif you need role-based access control. -
Add a
phone_numbercolumn tousersif you're using SMS auth. -
Encrypt
access_tokenandrefresh_tokenin production NextAuth doesn't do this by default.
What to leave alone
- Don't modify the
verification_tokenstable it's managed automatically. - Don't change the
session_tokenformat it's a signed JWT. - Don't add indexes to
provider_account_idunless you're querying it directly (it's already unique).
FAQ
Does NextAuth store passwords?
No. NextAuth is an OAuth-first library. It doesn't handle passwords. If you need email/password auth, use next-auth/providers/credentials with bcrypt, or use a service like Clerk or Lucia.
How do I see what's in my NextAuth tables?
Use dbdiagramr paste your connection string and get a visual schema of your NextAuth tables in seconds.
Can I add custom fields to the users table?
Yes. Add columns to the users table directly. NextAuth will ignore columns it doesn't know about, so you can safely add role, phone_number, preferences, etc.
What happens when a user deletes their account?
NextAuth doesn't cascade deletes by default. You need to manually delete from users, accounts, and sessions. Or add ON DELETE CASCADE to your foreign key constraints.
Is Auth.js the same as NextAuth?
Yes. Auth.js is the rebranded version of NextAuth. The database schema is identical. If you're on NextAuth v4, you're using the same tables.
Top comments (0)