Capítulo 102 de 108

Authentication (Registry)

Core Idea

Patterns for securing a private/personalized registry: token schemes on the client (components.json), matching server-side validation, and security best practices (env vars, HTTPS, rate limiting, custom error messages).

Key Concepts

  • Bearer token: headers: { "Authorization": "Bearer ${REGISTRY_TOKEN}" } in components.json, value pulled from .env.local.
  • API key header: headers: { "X-API-Key": "${API_KEY}" }, optionally with extra headers like X-Workspace-Id.
  • Query parameter auth: params: { "token": "${ACCESS_TOKEN}" } appended to the item URL — simplest scheme, weakest (token in URL/logs).
  • ${VAR_NAME} expansion: Environment variables are expanded from process.env at request time in URLs, headers and params; never logged by the CLI.
  • Custom error messages: A registry server can return { "error": "...", "message": "..." } in the response body on 401/403; the CLI surfaces message directly to the user.
  • Team/user-personalized registries: Server derives team/user from the token and serves different component sets or versions per identity.

Code Examples

{
  "registries": {
    "@private": {
      "url": "https://registry.company.com/{name}.json",
      "headers": { "Authorization": "Bearer ${REGISTRY_TOKEN}" }
    }
  }
}
  • O que demonstra: padrão canônico de autenticação por Bearer token, com o valor vindo de env var, nunca hardcoded.
export async function GET(request: NextRequest, { params }: { params: { name: string } }) {
  const authHeader = request.headers.get("authorization")
  const token = authHeader?.replace("Bearer ", "")
  if (!isValidToken(token)) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
  }
  if (!hasAccessToComponent(token, params.name)) {
    return NextResponse.json({ error: "Forbidden" }, { status: 403 })
  }
  const component = await getComponent(params.name)
  return NextResponse.json(component)
}
  • O que demonstra: validação server-side em duas etapas (token válido → depois, permissão pro item específico), retornando 401/403 corretamente.

Reference Tables

Auth methodcomponents.json fieldNotes
Bearer tokenheaders.Authorizationmost common
API keyheaders["X-API-Key"]can combine with workspace/tenant header
Query paramparams.tokensimplest, least secure
Basic authheaders.Authorization: "Basic ${BASE64_CREDENTIALS}"credentials pre-encoded
CLI errorMeaning
401 Unauthorizedtoken invalid or missing
403 Forbiddentoken valid but lacks permission
429 Too Many Requestsrate limit exceeded

Anti-patterns

  • Committing tokens to version control: always use .env.local + ${VAR} expansion, never literal secrets in components.json.
  • HTTP (not HTTPS) registry URLs: exposes tokens/headers in transit; always use https://.
  • No rate limiting on the registry endpoint: leaves it open to abuse; add middleware like express-rate-limit.
  • Static, never-rotated tokens: prefer expiring tokens with a expiresAt check server-side.

Key Takeaways

  1. Test locally with curl -H "Authorization: Bearer <token>" <url> and REGISTRY_TOKEN=<token> npx shadcn@latest add @private/button.
  2. Log access (userId, component, ip, userAgent, timestamp) for security/analytics on any private registry.
  3. Custom, human-readable error message fields in 401/403 responses meaningfully improve the CLI user experience (e.g. "subscription expired, renew at...").
  4. Multi-registry setups can mix public and differently-authenticated private registries (@public, @internal, @premium) side by side in one components.json.

Connects To

  • registry-namespace (ch104): namespace registries object is where these auth headers/params live.
  • mcp (ch097): MCP-driven installs of private registries need the same env vars set.