Errors and fixes

Registry 401, 402, 403: tokens, plans and permissions without guessing

Three codes, three causes: 401 is identity, 402 is payment, 403 is the right to access. Start the diagnosis from the command line, not from the browser.

Key points

  • 401 means a missing, malformed or expired header. 402 means the registry asks for payment. 403 means a valid token without rights on that resource.
  • Pass the token as a header configured in `components.json`, read from an environment variable.
  • A committed token is a compromised token: rotate it before any other fix.

Short answer: 401 means a missing or invalid identity, 402 means payment required, 403 means a valid identity with no right to the resource. Diagnose with curl before you touch the configuration.

The code table

CodeWhat it means in practiceFirst move
401header missing, token expired or malformedcheck the value and the expiry
402the plan does not include the resourcecheck the subscription, not the token
403valid token, missing permissioncheck the scope or the namespace
404wrong namespace or namecheck the slug in the registry

The mechanism: the order in which the registry answers

A private registry checks three things in sequence: who you are, whether you paid, what you may read. The first check produces 401 when the header is missing, malformed or expired. The second produces 402 when the identity is valid and the entitlement is not. The third produces 403 when the identity is valid, the plan exists and the scope or namespace does not cover that resource. Order matters: an expired token on a paid resource returns 401, not 402, so the code alone never tells the whole story. The CLI prints the status and discards the body, which is where the registry explains itself.

A diagnostic step: read the body, not just the code

The command below prints the status, the WWW-Authenticate header and the first characters of the body in one line: the first speaks about the gateway, the last about the registry. If the variable is empty the header goes out with no token and the 401 is already explained. If WWW-Authenticate is absent, the answer does not come from the service but from a proxy in front of it. If the body names a scope, the token is fine and the permission is not.

node -e "fetch('https://registry.acme.dev/r/button.json',{
  headers:{Authorization:'Bearer '+process.env.ACME_REGISTRY_TOKEN}
}).then(async r=>console.log(
  r.status, r.headers.get('www-authenticate'), (await r.text()).slice(0,120)))"

The right configuration

// components.json
{
  "registries": {
    "@acme": {
      "url": "https://registry.acme.dev/r/{name}.json",
      "headers": {
        "Authorization": "Bearer ${ACME_REGISTRY_TOKEN}"
      }
    }
  }
}

The token lives in an environment variable, not in the file. If the file was already committed with the token in plain text, rotate it. Removing it from the history does not undo the copy someone already downloaded.

Test without installing

curl -i -H "Authorization: Bearer $ACME_REGISTRY_TOKEN" \
  https://registry.acme.dev/r/button.json

The status code answers all three questions at once. A 200 means the problem is in the client configuration, a 401 means the token never arrives, a 403 means the token is recognised and the permission is missing.

When the fix does not work

If curl works and the client does not, the variable never reaches the process. That happens in CI without secrets, in containers without env_file, or in a command run from a context that does not inherit the environment. Check with node -p process.env.ACME_REGISTRY_TOKEN in the same context. A second case is a proxy that strips the Authorization header: then the 401 comes from the gateway, not from the registry.

When the common fix makes things worse

A 401 usually triggers a token rotation. If the 401 came from a proxy stripping Authorization, a new token changes nothing and now you own two variables: a rotated secret and a header that is still cut. Rotating before isolating the layer makes the problem harder to read, because the old token worked in curl and the new one does not. Isolate first: same command, same variable, run from the context the client runs in. If curl passes and the client does not, the token is healthy and the transport is the problem.

A limitation with a name: two identical 401s

The 401 code does not separate a token that never arrived from a token that arrived and was rejected: both are 401, and a gateway can generate its own before the request ever reaches the registry. The limitation is called 401 indistinguishability; you narrow it with extra headers, a WWW-Authenticate carrying the reason, an X-Registry-Request-Id to correlate in the logs, or by giving the gateway and the service two different codes: a choice many registries decline to make.

How to prevent it

  • One token per registry, with a short expiry and minimal permissions.
  • No token in components.json, not even a placeholder that looks real.
  • A CI test that installs one component from every configured registry.

The CI test is worth more than a checklist because it sees the real registry. Add a second check for 402: an endpoint answering 402 with no alert is a customer discovering the problem at install time instead of before it.

Where to go next

In the catalogue: components, pricing block, icons and catalogue rankings. The guides: private registries, tokens and namespaces and free and premium themes; the comparison: 1,556 blocks with a preview.

Frequently asked questions

Aren't 401 and 403 the same thing?

No. A 401 says the server does not know who you are, a 403 says it knows and you still cannot read that resource. The fixes differ.

Where do I put the token?

In an environment variable that components.json reads, never in plain text in the committed file. In CI the variable comes from the secret store.

How do I check the token without installing anything?

With curl -i -H "Authorization: Bearer $TOKEN" against the resource URL. The status code tells you whether the problem is the token or the resource.

Can a 402 arrive with a valid token?

Yes. It means the identity is recognised but the plan does not include that resource, so you need a purchase or an upgrade, not another token.

Can I use public registries without headers?

Yes, if the registry needs no authentication. Namespace configuration still helps with resolution order.