Skip to content

fix: Use PRIVATE-TOKEN header for PAT authentication instead of Bearer #187

@polaz

Description

@polaz

Problem

Currently, the server always uses Authorization: Bearer <token> for all authentication types (both PAT and OAuth tokens). While GitLab accepts Bearer for PATs in most cases, the canonical header for Personal Access Tokens is PRIVATE-TOKEN.

Current behavior

In src/utils/fetch.ts:187 (getAuthorizationHeader()):

export function getAuthorizationHeader(): string | undefined {
  const token = getGitLabToken();
  return token ? `Bearer ${token}` : undefined;
}

In src/services/ConnectionManager.ts:62:

const clientOptions = oauthMode
  ? {}
  : { headers: { Authorization: `Bearer ${GITLAB_TOKEN}` } };

Both always use Bearer regardless of token type.

Expected behavior

  • Static mode (PAT): Use PRIVATE-TOKEN: <token> header
  • OAuth mode: Use Authorization: Bearer <token> header

Why this matters

  1. PRIVATE-TOKEN is GitLab's canonical header for PATs and is guaranteed to work across all API surfaces (REST, GraphQL)
  2. Some edge cases with newer token formats (rotating tokens, tokens with dots) may behave differently with Bearer
  3. Better alignment with GitLab's own documentation and best practices
  4. Clearer separation of authentication mechanisms

Proposed fix

Update getAuthorizationHeader() in src/utils/fetch.ts to return the appropriate header based on auth mode:

export function getAuthHeaders(): Record<string, string> {
  const token = getGitLabToken();
  if (!token) return {};
  
  if (isOAuthEnabled()) {
    return { Authorization: `Bearer ${token}` };
  }
  return { "PRIVATE-TOKEN": token };
}

Update ConnectionManager.ts similarly for the GraphQL client initialization.

Files to modify

  • src/utils/fetch.ts - getAuthorizationHeader() -> getAuthHeaders()
  • src/services/ConnectionManager.ts - Client options construction
  • src/graphql/client.ts - Header merging (if needed)
  • Related tests

Additional context

Discovered when a user with a read_user-only scoped token got 401 on GraphQL but 200 on REST. While the root cause was insufficient scopes (docs correctly require api), investigating revealed the auth header type mismatch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions