CORS requests fail for Docker registry

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

  • Close this issue

Summary

I am trying to connect to GitLab Docker registry to list all Docker images for a project. But it seems it is not possible to connect to necessary APIs from a browser because they do not support CORS requests. I am in particular interested in two endpoints:

  • /jwt/auth
  • /v2/:dockerImage/tags/list

Steps to reproduce

I have the following script:

const API_USERNAME = '...';
const API_ACCESS_KEY = '...';
const DOCKER_IMAGE = '...';
const CLIENT_ID = '...';
const GITLAB_HOSTNAME = '...';
const REGISTRY_HOSTNAME = '...';

if (typeof btoa === 'undefined') {
  function btoa(b) {
    return Buffer.from(b).toString('base64');
  }
}

if (typeof fetch === 'undefined') {
  fetch = require('node-fetch');
}

(async function () {
  const authorization = btoa(`${API_USERNAME}:${API_ACCESS_KEY}`);

  let response, result;

  response = await fetch(`https://${GITLAB_HOSTNAME}/jwt/auth?service=container_registry&scope=repository:${DOCKER_IMAGE}:pull&client_id=${CLIENT_ID}`, {
    mode: 'cors',
    credentials: 'omit',
    headers: {
      'Authorization': `Basic ${authorization}`,
    },
  });

  if (response.status !== 200) {
    throw new Error(`Fetch error: ${response.statusText}`);
  }

  result = await response.json();

  const token = result.token;

  if (!token) {
    throw new Error("Token not provided.");
  }

  response = await fetch(`https://${REGISTRY_HOSTNAME}/v2/${DOCKER_IMAGE}/tags/list`, {
    mode: 'cors',
    credentials: 'omit',
    headers: {
      'Authorization': `Bearer ${token}`,
    },
  });

  if (response.status !== 200) {
    throw new Error(`Fetch error: ${response.statusText}`);
  }

  result = await response.json();

  console.log(result);
})();

What is the current bug behavior?

The script above works great in node.js, but in a browser both requests fail. The first responds to the pre-flight OPTIONS request, but it does not set CORS headers. The second one (if I hard-code the token) fails the pre-flight OPTIONS request with 401 Unauthorized because for pre-flight the browser does not send the Authorization header.

What is the expected correct behavior?

Both API endpoints should correctly respond to pre-flight CORS OPTIONS requests.

Edited Aug 21, 2025 by 🤖 GitLab Bot 🤖
Assignee Loading
Time tracking Loading