BloopAI/vibe-kanban · error · ApiError

Unauthorized

Error message

Unauthorized

What it means

oauthApi.getToken() calls GET /api/auth/token to fetch the current remote-server access token and throws ApiError('Unauthorized', 401) when that endpoint answers 401. This library throws it explicitly (instead of relying on handleApiResponse) so callers can detect an unauthenticated/expired session for the token endpoint specifically. It means the server refused to issue a token because the user has no valid session or refresh credentials.

Source

Thrown at packages/web-core/src/shared/lib/api.ts:1302

  logout: async (): Promise<void> => {
    const response = await makeRequest('/api/auth/logout', {
      method: 'POST',
    });
    if (!response.ok) {
      throw new ApiError(
        `Logout failed with status ${response.status}`,
        response.status,
        response
      );
    }
  },

  /** Returns the current access token for the remote server (auto-refreshes if needed) */
  getToken: async (): Promise<TokenResponse> => {
    const response = await makeRequest('/api/auth/token');
    if (response.status === 401) {
      throw new ApiError('Unauthorized', 401, response);
    }
    return handleApiResponse<TokenResponse>(response);
  },

  /** Returns the user ID of the currently authenticated user */
  getCurrentUser: async (): Promise<CurrentUserResponse> => {
    const response = await makeRequest('/api/auth/user');
    return handleApiResponse<CurrentUserResponse>(response);
  },
};

/**
 * @deprecated Use `tokenManager.getToken()` from
 * `@/shared/lib/auth/tokenManager` instead.
 * This function does not handle 401 responses or token refresh coordination.
 */
export async function getCachedToken(): Promise<string | null> {
  const { tokenManager } = await import('@/shared/lib/auth/tokenManager');

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Check the response status and redirect the user to the login flow (e.g. oauthApi.handoffInit or localLogin) instead of retrying.
  2. Call oauthApi.status() or authMethods() first to confirm whether auth is required and a session exists.
  3. Clear stale client auth state and force a fresh login, then retry getToken().
  4. Verify the backend session store (cookies/refresh tokens) is persistent and not being cleared on restart.

Example fix

// before
const token = await oauthApi.getToken();
// after
try {
  const token = await oauthApi.getToken();
} catch (e) {
  if (e instanceof ApiError && e.status === 401) {
    window.location.assign('/login?returnTo=' + encodeURIComponent(window.location.pathname));
    return;
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const status = await oauthApi.status();
if (!status.authenticated) redirectToLogin();

Type guard

function isAuthError(e: unknown): e is ApiError {
  return e instanceof ApiError && e.status === 401;
}

Try / catch

try {
  const token = await oauthApi.getToken();
} catch (e) {
  if (isAuthError(e)) { redirectToLogin(); return; }
  throw e;
}

Prevention

When it happens

Trigger: The user's session cookie is missing/expired when /api/auth/token is called; the refresh token stored server-side is revoked or expired; the request is made before any login has occurred; server-side session was invalidated (logout elsewhere, server restart clearing sessions).

Common situations: Remote deployments where the browser session cookie has expired after idle timeout; calling token-gated features without completing OAuth/local login; server restarted and wiped in-memory session store; user token revoked after password change.

Understand the failure class

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/b8c407fab8b4068b. Report an issue: GitHub.