返回 Skill 列表
extension
分类: 开发与工程无需 API Key

dashboard-api-authentication

仪表盘前端API调用的正确认证模式。在添加新的API端点、创建获取数据的组件或调试401未授权错误时使用。涵盖JWT令牌处理、apiRequest辅助函数以及常见陷阱。

person作者: jakexiaohubgithub

Dashboard API Authentication

Quick Reference

// ALWAYS use api.ts functions for authenticated requests
import { getFeatureFlags, setFeatureFlagOverride } from '../api';

// NEVER use raw fetch for authenticated endpoints
// BAD: fetch('/api/feature-flags', { credentials: 'include' })
// GOOD: getFeatureFlags()

The Golden Rule

All authenticated API calls MUST use the apiRequest helper from api.ts or one of the exported API functions that use it internally.

The dashboard uses JWT-based authentication. The token is stored in localStorage and sent via the Authorization: Bearer <token> header. Raw fetch() calls with credentials: 'include' will NOT include this header.

Authentication Architecture

Token Storage

// Token is stored in multiple places for different purposes:
localStorage.setItem('dashboard_token', token); // Primary storage
setCookie('auth_token', token, 1); // For nginx auth (production)

The apiRequest Helper

Located in packages/dashboard-frontend/src/api.ts:

async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
  const token = getAuthToken(); // Gets from localStorage or cookie

  const headers: HeadersInit = {
    'Content-Type': 'application/json',
    ...options.headers,
  };

  if (token) {
    // THIS IS THE KEY - adds Authorization header
    (headers as Record<string, string>)['Authorization'] = `Bearer ${token}`;
  }

  const response = await fetch(`${API_BASE}${endpoint}`, {
    ...options,
    headers,
  });

  // Handles 401 by clearing token and reloading
  if (response.status === 401) {
    clearAuthToken();
    window.location.reload();
    throw new Error('Authentication expired');
  }

  // ... rest of response handling
}

Common Patterns

Adding a New API Function

// In packages/dashboard-frontend/src/api.ts

// For GET requests
export async function getMyData(): Promise<MyDataResponse> {
  return apiRequest('/my-endpoint');
}

// For POST/PUT/DELETE requests
export async function updateMyData(id: string, data: UpdateInput): Promise<MyDataResponse> {
  return apiRequest(`/my-endpoint/${encodeURIComponent(id)}`, {
    method: 'PUT',
    body: JSON.stringify(data),
  });
}

Using API Functions in Components

// In a component or hook
import { getMyData, updateMyData } from '../api';

// In useEffect or event handler
const loadData = async () => {
  try {
    const data = await getMyData(); // Automatically authenticated
    setData(data);
  } catch (error) {
    // Handle error - may be auth error, network error, etc.
  }
};

Using API Functions in Custom Hooks

// In packages/dashboard-frontend/src/hooks/useMyFeature.ts
import { useCallback, useEffect, useState } from 'react';
import { getMyData, type MyDataResponse } from '../api';

export function useMyFeature() {
  const [data, setData] = useState<MyDataResponse | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const loadData = useCallback(async () => {
    try {
      setLoading(true);
      const result = await getMyData(); // Uses apiRequest internally
      setData(result);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to load');
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    loadData();
  }, [loadData]);

  return { data, loading, error, refresh: loadData };
}

Common Mistakes

Mistake 1: Using Raw Fetch

// BAD - Will get 401 Unauthorized
const response = await fetch('/api/feature-flags', {
  credentials: 'include', // This sends cookies, NOT the JWT header
});

// GOOD - Uses apiRequest which adds Authorization header
import { getFeatureFlags } from '../api';
const data = await getFeatureFlags();

Mistake 2: Forgetting to Export from api.ts

If you add a new endpoint, you must:

  1. Add the function to api.ts
  2. Export it
  3. Import it where needed
// In api.ts - add AND export
export async function getNewEndpoint(): Promise<Response> {
  return apiRequest('/new-endpoint');
}

// In component - import from api.ts
import { getNewEndpoint } from '../api';

Mistake 3: Not Handling 401 in Custom Fetch

If you absolutely must use raw fetch (rare), handle 401:

// Only if apiRequest can't be used (e.g., file uploads with progress)
const response = await fetch(url, {
  headers: {
    Authorization: `Bearer ${getAuthToken()}`,
  },
});

if (response.status === 401) {
  clearAuthToken();
  window.location.reload();
  throw new Error('Authentication expired');
}

Debugging 401 Errors

Symptoms

  • API calls return 401 Unauthorized
  • Server logs show "No authorization header"
  • Feature works initially but fails after page components load

Diagnostic Steps

  1. Check if using apiRequest:

    grep -r "fetch('/api" packages/dashboard-frontend/src/
    

    Any raw fetch to /api/* is suspicious.

  2. Check Network tab:

    • Look for Authorization header in request
    • If missing, the call isn't using apiRequest
  3. Verify token exists:

    // In browser console
    localStorage.getItem('dashboard_token');
    

Fix Pattern

Replace raw fetch with api.ts function:

// Before (broken)
const response = await fetch(`/api/feature-flags/${id}`, {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({ enabled }),
});

// After (working)
import { setFeatureFlagOverride } from '../api';
await setFeatureFlagOverride(id, enabled);

Server-Side Authentication

The server expects JWT in the Authorization header:

// In packages/dashboard/src/auth.ts
authMiddleware = async (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader) {
    res.status(401).json({ error: 'No authorization header' });
    return;
  }

  const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader;

  const payload = this.verifyToken(token);
  // ... rest of verification
};

Checklist for New API Endpoints

When adding a new authenticated endpoint:

  • [ ] Create the route in packages/dashboard/src/server/routes/
  • [ ] Apply requireAuth middleware to the route
  • [ ] Add API function in packages/dashboard-frontend/src/api.ts
  • [ ] Export the function from api.ts
  • [ ] Use the exported function in components/hooks (never raw fetch)
  • [ ] Test that 401 is properly handled if token expires

Testing Authentication

To verify authentication is working:

// In a test or console
import { getAuthToken } from '../api';

// Check token exists
const token = getAuthToken();
console.log('Token present:', !!token);

// Make authenticated request
try {
  const data = await getFeatureFlags();
  console.log('Auth working:', data);
} catch (e) {
  console.error('Auth failed:', e);
}