# Handling Authentication Errors

> Required implementation pattern for handling AppTokenValidationError (401) responses when feature tokens expire. Use when: 1. Building any Fusebase Apps feature that makes API calls, 2. Implementing authentication error handling, 3. Creating AuthExpiredModal components, 4. Setting up global error handlers in App.tsx. All features MUST implement this pattern to handle token expiration gracefully.

- Skill: `antongulin/handling-authentication-errors` (Agent Skill)
- Install (CLI): `npx skillmds@latest add antongulin/handling-authentication-errors`
- Raw SKILL.md: https://api.skillmd.com/api/skills/antongulin/handling-authentication-errors/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: antongulin (https://skillmd.com/u/antongulin)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/antongulin/handling-authentication-errors

---


# Handling Authentication Errors

All features **MUST** handle `AppTokenValidationError` responses from the API. When the feature token expires, the API returns a 401 with this body:

```json
{
  "name": "AppTokenValidationError",
  "message": "Fail to validate app token",
  "reason": "expired"
}
```

## Implementation

### 1. Detect `AppTokenValidationError` in API calls

The error name may appear at different nesting levels depending on the SDK. Check all of them:

```typescript
function isAppTokenValidationError(error: unknown): boolean {
  if (error && typeof error === 'object') {
    const err = error as any
    const names = [err.name, err.data?.name, err.error?.name, err.body?.name]
    return names.includes('AppTokenValidationError')
  }
  return false
}
```

Create a custom `AuthTokenExpiredError` class. In every API call's catch block, check with the function above and throw `AuthTokenExpiredError` if matched; otherwise rethrow.

### 2. Show a "Session Expired" modal

When `AuthTokenExpiredError` is caught at the UI level, display a centered modal:

- **Title**: "Session Expired"
- **Message**: "Your authentication expired, please refresh the page to authenticate again."
- **Buttons**: "Refresh page" (calls `window.location.reload()`) and "Cancel" (closes modal)

Manage modal open/close state in `App.tsx` and pass an `onAuthError` callback to child components that make API calls.

## Critical: `/users/me` Exception

The `/users/me` endpoint **MUST NOT** trigger `AuthTokenExpiredError`.

When a feature is **public**, anonymous visitors receive a 401 from `/users/me`. This is expected — it means "not authenticated", NOT "session expired". Throwing `AuthTokenExpiredError` here causes the Session Expired modal to appear immediately for every anonymous visitor.

```typescript
export async function fetchCurrentUser(
  featureToken: string
): Promise<{ id: number; email: string } | null> {
  try {
    const response = await fetch(
      'https://app-api.{FUSEBASE_HOST}/v4/api/users/me',
      { headers: { 'x-app-feature-token': featureToken } }
    )
    if (!response.ok) return null // Do NOT throw AuthTokenExpiredError
    return await response.json()
  } catch {
    return null
  }
}
```

### Rule of thumb

- **`/users/me` 401** → return `null` (user is anonymous/guest)
- **Dashboard/data API 401 with `AppTokenValidationError`** → throw `AuthTokenExpiredError` (session expired)

