13 KiB
Authentication in PocketBase
Overview
PocketBase provides comprehensive authentication features including:
- Email/password authentication
- OAuth2 integration (Google, GitHub, etc.)
- Magic link authentication
- Email verification
- Password reset
- JWT token management
- Role-based access control
Auth Collections
User accounts are managed through Auth Collections. Unlike base collections, auth collections:
- Have built-in authentication fields
- Support OAuth2 providers
- Provide password management
- Include email verification
- Generate JWT tokens
Built-in Auth Fields
{
"id": "string (unique)",
"email": "string (required, unique)",
"password": "string (hashed)",
"passwordConfirm": "string (validation only)",
"emailVisibility": "boolean (default: true)",
"verified": "boolean (default: false)",
"created": "datetime (autodate)",
"updated": "datetime (autodate)",
"lastResetSentAt": "datetime",
"verificationToken": "string"
}
Note: Password fields are never returned in API responses for security.
Registration
Email/Password
// Register new user
const authData = await pb.collection('users').create({
email: 'user@example.com',
password: 'password123',
passwordConfirm: 'password123',
name: 'John Doe' // custom field
});
// Returns:
// {
// token: "JWT_TOKEN",
// user: { ...user data... }
// }
Features:
- Automatic password hashing
- Email uniqueness validation
- Email verification (if enabled)
- Custom fields supported
OAuth2 Registration/Login
// With OAuth2 code (from provider redirect)
const authData = await pb.collection('users').authWithOAuth2({
provider: 'google',
code: 'OAUTH2_CODE_FROM_GOOGLE'
});
// With existing access token
const authData = await pb.collection('users').authWithOAuth2({
provider: 'github',
accessToken: 'USER_ACCESS_TOKEN'
});
Supported Providers:
- GitHub
- GitLab
- Discord
- Microsoft
- Spotify
- Twitch
- Discord
- Twitter/X
Custom OAuth2 Configuration:
- Go to Auth Collections → OAuth2 providers
- Add provider with client ID/secret
- Configure redirect URL
- Enable provider
Magic Link Authentication
// Send magic link
await pb.collection('users').requestPasswordReset('user@example.com');
// User clicks link (URL contains token)
// Reset password (returns 204 on success)
await pb.collection('users').confirmPasswordReset(
'RESET_TOKEN',
'newPassword123',
'newPassword123'
);
// After confirming, prompt the user to sign in again with the new password.
Login
Standard Login
// Email and password
const authData = await pb.collection('users').authWithPassword(
'user@example.com',
'password123'
);
// Access token and user data
console.log(authData.token); // JWT token
console.log(authData.user); // User record
// Token is automatically stored
console.log(pb.authStore.token); // Access stored token
OAuth2 Login
// Same as registration - creates user if doesn't exist
const authData = await pb.collection('users').authWithOAuth2({
provider: 'google',
code: 'OAUTH2_CODE'
});
Magic Link Login
// Request magic link
await pb.collection('users').requestVerification('user@example.com');
// User clicks verification link (returns 204 on success)
await pb.collection('users').confirmVerification('VERIFICATION_TOKEN');
// Verification does not log the user in automatically; call authWithPassword or another auth method.
Auth State Management
Checking Auth Status
// Check if user is authenticated
if (pb.authStore.isValid) {
const user = pb.authStore.model;
console.log('User is logged in:', user.email);
} else {
console.log('User is not logged in');
}
// Get current user
const user = pb.authStore.model;
// Refresh auth state
await pb.collection('users').authRefresh();
Auth Store Persistence
The default auth store persists tokens in localStorage when available and falls back to an in-memory store otherwise. Call pb.authStore.clear() to invalidate the current session. For custom storage implementations, extend the SDK BaseAuthStore as described in the official JS SDK README.
React Auth Hook
import { useEffect, useState } from 'react';
import PocketBase from 'pocketbase';
function useAuth(pb) {
const [user, setUser] = useState(pb.authStore.model);
useEffect(() => {
const unsub = pb.authStore.onChange((token, model) => {
setUser(model);
});
return () => unsub();
}, []);
return { user };
}
// Usage
function App() {
const pb = new PocketBase('http://127.0.0.1:8090');
const { user } = useAuth(pb);
return user ? (
<div>Welcome, {user.email}!</div>
) : (
<div>Please log in</div>
);
}
Logout
// Clear auth state
pb.authStore.clear();
// After logout, authStore.model will be null
console.log(pb.authStore.model); // null
Protected Routes (Frontend)
React Router Protection
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children }) {
const { user } = useAuth(pb);
if (!user) {
return <Navigate to="/login" />;
}
return children;
}
// Usage
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
Vanilla JS Protection
// Check before API call
function requireAuth() {
if (!pb.authStore.isValid) {
window.location.href = '/login';
return false;
}
return true;
}
// Usage
if (requireAuth()) {
const posts = await pb.collection('posts').getList(1, 50);
}
User Profile Management
Update User Data
// Update user profile
const updated = await pb.collection('users').update(user.id, {
name: 'Jane Doe',
bio: 'Updated bio',
avatar: fileInput.files[0] // File upload
});
// Only authenticated user can update their own profile
// unless using admin API
Change Password
// Change password (requires current password)
const updated = await pb.collection('users').update(user.id, {
oldPassword: 'currentPassword123',
password: 'newPassword123',
passwordConfirm: 'newPassword123'
});
Update Email
// Update email (triggers verification if enabled)
const updated = await pb.collection('users').update(user.id, {
email: 'newemail@example.com'
});
Email Verification
Enable Email Verification
- Go to Auth Collections → Options
- Enable "Email verification"
- Customize verification page
- Save
Manual Verification
// Request verification email
await pb.collection('users').requestVerification('user@example.com');
// User clicks link with token
const authData = await pb.collection('users').confirmVerification('TOKEN_FROM_URL');
Auto-Verify on OAuth
// OAuth users can be auto-verified
// Configure in Auth Collections → OAuth2 providers
// Check "Auto-verification"
Password Reset
Request Reset
// Send password reset email
await pb.collection('users').requestPasswordReset('user@example.com');
Confirm Reset
// User clicks link from email
// Reset with token from URL
const authData = await pb.collection('users').confirmPasswordReset(
'TOKEN_FROM_URL',
'newPassword123',
'newPassword123'
);
OAuth2 Configuration
Google OAuth2 Setup
-
Google Cloud Console
- Create new project or select existing
- Enable Google+ API
- Create OAuth 2.0 credentials
- Add authorized redirect URIs:
http://localhost:8090/api/oauth2/google/callbackhttps://yourdomain.com/api/oauth2/google/callback
-
PocketBase Admin UI
- Go to Auth Collections → OAuth2 providers
- Click "Google"
- Enter Client ID and Client Secret
- Save
GitHub OAuth2 Setup
-
GitHub Developer Settings
- New OAuth App
- Authorization callback URL:
http://localhost:8090/api/oauth2/github/callback
- Get Client ID and Secret
-
PocketBase Admin UI
- Configure GitHub provider
- Enter credentials
Custom OAuth2 Provider
// Most providers follow similar pattern:
// 1. Redirect to provider auth page
// 2. Provider redirects back with code
// 3. Exchange code for access token
// 4. Use access token with PocketBase
// Example: Discord
window.location.href =
`https://discord.com/api/oauth2/authorize?client_id=CLIENT_ID&redirect_uri=${encodeURIComponent('http://localhost:8090/_/')}&response_type=code&scope=identify%20email`;
JWT Token Details
Token Structure
JWT tokens consist of three parts:
- Header - Algorithm and token type
- Payload - User data and claims
- Signature - HMAC validation
// Payload includes (fields may vary depending on the auth collection):
{
"id": "USER_ID",
"collectionId": "COLLECTION_ID",
"collectionName": "users",
"exp": 1234567890, // expires at
"iat": 1234567890 // issued at
}
Token Expiration
- Default expiration: 7 days
- Can be customized in Auth Collections → Options
- Tokens remain valid until
exp; callpb.collection('users').authRefresh()to refresh.
Manual Token Validation
// Check if token is still valid
if (pb.authStore.isValid) {
// Token is valid
const user = pb.authStore.model;
} else {
// Token expired or invalid
// Redirect to login
}
Security Best Practices
Password Security
// Configure in Auth Collections → Options
{
"minPasswordLength": 8,
"requirePasswordUppercase": true,
"requirePasswordLowercase": true,
"requirePasswordNumbers": true,
"requirePasswordSymbols": true
}
Account Security
-
Enable Email Verification
- Prevent fake accounts
- Verify user email ownership
-
Implement Rate Limiting
- Prevent brute force attacks
- Configure at reverse proxy level
-
Use HTTPS in Production
- Encrypt data in transit
- Required for OAuth2
-
Set Appropriate Token Expiration
- Balance security and UX
- Consider refresh tokens
-
Validate OAuth State
- Prevent CSRF attacks
- Implement proper state parameter
Common Auth Rules
Users can only access their own data:
user_id = @request.auth.id
Verified users only:
@request.auth.verified = true
Admins only:
@request.auth.role = 'admin'
Role-based access:
@request.auth.role = 'moderator' || @request.auth.role = 'admin'
Multi-Tenant Authentication
Workspace/Team Model
// collections:
// - users (auth) - email, password
// - workspaces (base) - name, owner_id
// - workspace_members (base) - workspace_id, user_id, role
// Users can access workspaces they're members of:
List Rule: "id != '' && (@request.auth.id != '')"
View Rule: "members.user_id ?~ @request.auth.id"
// On login, filter workspace by user membership
async function getUserWorkspaces() {
const memberships = await pb.collection('workspace_members').getList(1, 100, {
filter: `user_id = "${pb.authStore.model.id}"`
});
const workspaceIds = memberships.items.map(m => m.workspace_id);
return workspaceIds;
}
Auth API Reference
User Methods
// Auth collection methods
pb.collection('users').create() // Register
pb.collection('users').authWithPassword() // Login
pb.collection('users).authWithOAuth2() // OAuth2
pb.collection('users).authRefresh() // Refresh
pb.collection('users).requestVerification() // Send verification
pb.collection('users).confirmVerification() // Verify
pb.collection('users).requestPasswordReset() // Reset request
pb.collection('users).confirmPasswordReset() // Confirm reset
// Admin methods
pb.collection('users').getOne(id) // Get user
pb.collection('users).update(id, data) // Update user
pb.collection('users).delete(id) // Delete user
pb.collection('users').listAuthMethods() // List allowed auth methods and OAuth providers
Troubleshooting
Login not working
- Check email/password correctness
- Verify user exists
- Check if account is verified (if verification required)
- Check auth rules don't block access
OAuth2 redirect errors
- Verify redirect URI matches exactly
- Check provider configuration
- Ensure HTTPS in production
- Check CORS settings
Token expired
- Use authRefresh() to get new token
- Check token expiration time
- Implement auto-refresh logic
Password reset not working
- Check if email exists
- Verify reset link wasn't used
- Check spam folder
- Verify email sending configuration
Can't access protected data
- Check auth rules
- Verify user is authenticated
- Check user permissions
- Verify collection rules
Related Topics
- Collections - Auth collection details
- API Rules & Filters - Security rules
- Files Handling - File uploads
- Security Rules - Comprehensive access control
- Going to Production - Production security