Files
shiftcraft/.claude/skills/pocketbase-best-practices/rules/auth-impersonation.md
2026-04-17 23:26:01 +00:00

3.7 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Use Impersonation for Admin Operations MEDIUM Safe admin access to user data without password sharing authentication, admin, impersonation, superuser

Use Impersonation for Admin Operations

Impersonation allows superusers to generate tokens for other users, enabling admin support tasks and API key functionality without sharing passwords.

Incorrect (sharing credentials or bypassing auth):

// Bad: sharing user passwords for support
async function helpUser(userId, userPassword) {
  await pb.collection('users').authWithPassword(userEmail, userPassword);
  // Support team knows user's password!
}

// Bad: directly modifying records without proper context
async function fixUserData(userId) {
  // Bypasses user's perspective and rules
  await pb.collection('posts').update(postId, { fixed: true });
}

Correct (using impersonation):

import PocketBase from 'pocketbase';

// Admin client with superuser auth (use environment variables, never hardcode)
const adminPb = new PocketBase(process.env.PB_URL);
await adminPb.collection('_superusers').authWithPassword(
  process.env.PB_SUPERUSER_EMAIL,
  process.env.PB_SUPERUSER_PASSWORD
);

async function impersonateUser(userId) {
  // Generate impersonation token (non-renewable)
  const impersonatedClient = await adminPb
    .collection('users')
    .impersonate(userId, 3600);  // 1 hour duration

  // impersonatedClient has user's auth context
  console.log('Acting as:', impersonatedClient.authStore.record.email);

  // Operations use user's permissions
  const userPosts = await impersonatedClient.collection('posts').getList();

  return impersonatedClient;
}

// Use case: Admin viewing user's data
async function adminViewUserPosts(userId) {
  const userClient = await impersonateUser(userId);

  // See exactly what the user sees (respects API rules)
  const posts = await userClient.collection('posts').getList();

  return posts;
}

// Use case: API keys for server-to-server communication
async function createApiKey(serviceUserId) {
  // Create a service impersonation token (use short durations, rotate regularly)
  const serviceClient = await adminPb
    .collection('service_accounts')
    .impersonate(serviceUserId, 86400);  // 24 hours max, rotate via scheduled task

  // Return token for service to use
  return serviceClient.authStore.token;
}

// Using API key token in another service
async function useApiKey(apiToken) {
  const pb = new PocketBase('http://127.0.0.1:8090');

  // Manually set the token
  pb.authStore.save(apiToken, null);

  // Now requests use the service account's permissions
  const data = await pb.collection('data').getList();
  return data;
}

Important considerations:

// Impersonation tokens are non-renewable
const client = await adminPb.collection('users').impersonate(userId, 3600);

// This will fail - can't refresh impersonation tokens
try {
  await client.collection('users').authRefresh();
} catch (error) {
  // Expected: impersonation tokens can't be refreshed
}

// For continuous access, generate new token when needed
async function getImpersonatedClient(userId) {
  // Check if existing token is still valid
  if (cachedClient?.authStore.isValid) {
    return cachedClient;
  }

  // Generate fresh token
  return await adminPb.collection('users').impersonate(userId, 3600);
}

Security best practices:

  • Use short durations for support tasks
  • Log all impersonation events
  • Restrict impersonation to specific admin roles
  • Never expose impersonation capability in client code
  • Use dedicated service accounts for API keys

Reference: PocketBase Impersonation