# Authentication

> Shared VCUP_TOKEN secret: Bearer checks on upload and delete, client token in ~/.vcuprc or VCUP_TOKEN env, and which routes are public.

- Repository: MaxLeiter/vcup
- GitHub: https://github.com/MaxLeiter/vcup
- Human docs: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb
- Complete Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/llms-full.txt

## Source Files

- `api/upload.ts`
- `api/delete.ts`
- `cli.ts`
- `.env.example`
- `README.md`

---

---
title: "Authentication"
description: "Shared VCUP_TOKEN secret: Bearer checks on upload and delete, client token in ~/.vcuprc or VCUP_TOKEN env, and which routes are public."
---

vcup protects mutating API routes with a single shared secret: the server reads `VCUP_TOKEN` from the deployment environment, and `POST /api/upload` and `DELETE /api/delete` reject requests unless the `Authorization` header carries `Bearer <token>` with an exact match. The CLI loads the same value from `VCUP_TOKEN` or `~/.vcuprc` and sends it on every upload and delete. File serving at `/f/:slug` is unauthenticated.

## Shared secret model

| Role | Variable | Where set |
|------|----------|-----------|
| Server | `VCUP_TOKEN` | Vercel project env (see `.env.example`) |
| CLI | `token` in `~/.vcuprc` | User home directory |
| CLI | `VCUP_TOKEN` | Shell environment |

There is no per-user identity, token rotation API, or scoped permissions. Anyone who knows `VCUP_TOKEN` can upload and delete through the API. Anyone who has a proxy or raw blob URL can read the file without the secret.

<Warning>
Uploaded blobs are stored with `access: "public"` in Vercel Blob. Proxy URLs and raw blob URLs are readable without `VCUP_TOKEN`. Authentication gates writes and deletes, not read access to objects you have already shared.
</Warning>

## Protected routes

Both handlers use the same check before any business logic:

```ts
const token = req.headers.authorization?.replace("Bearer ", "");
if (!process.env.VCUP_TOKEN || token !== process.env.VCUP_TOKEN) {
  res.statusCode = 401;
  res.end("Unauthorized");
  return;
}
```

| Route | Method | Auth required | Handler |
|-------|--------|---------------|---------|
| `/api/upload` | `POST` | Yes — `Authorization: Bearer <VCUP_TOKEN>` | `api/upload.ts` |
| `/api/delete` | `DELETE` | Yes — `Authorization: Bearer <VCUP_TOKEN>` | `api/delete.ts` |
| `/f/:slug` (rewritten to `/api/f`) | `GET` | No | `api/f.ts` via `vercel.json` |

:::endpoint POST /api/upload
Requires a valid Bearer token and `X-Filename` before streaming the body to Vercel Blob.
:::

<ParamField header="Authorization" type="string" required>
`Bearer <VCUP_TOKEN>` — prefix is stripped with a single `replace("Bearer ", "")` on the header value.
</ParamField>

<ResponseField name="status" type="number">
`401` with body `Unauthorized` when the server env is unset, the header is missing, or the token does not match exactly.
</ResponseField>

:::endpoint DELETE /api/delete
Requires a valid Bearer token and `X-Blob-Url` before calling Vercel Blob `del`.
:::

<ParamField header="Authorization" type="string" required>
Same Bearer scheme as upload.
</ParamField>

<ResponseField name="status" type="number">
`401` with body `Unauthorized` under the same conditions as upload.
</ResponseField>

Other status codes on these routes (for example `405` for wrong method, `400` for missing headers) are returned only after authentication succeeds.

## Public routes

`vercel.json` rewrites `/f/:slug*` to `/api/f`. The proxy handler accepts `GET` only, does not read `Authorization`, and streams content from `BLOB_STORE_URL` plus the slug path.

| Surface | Auth | Notes |
|---------|------|-------|
| `GET /f/<slug>` | None | Inline `Content-Disposition`; long cache headers |
| Raw Vercel Blob URL (`raw` in upload JSON) | None | Public blob store URL returned at upload time |

<Info>
Knowing a slug or raw URL is sufficient to download a file. Treat shareable links as capability URLs, not as proof of identity.
</Info>

## CLI client credentials

`cli.ts` loads credentials in `loadConfig()` before upload or delete:

1. If **both** `VCUP_API_URL` and `VCUP_TOKEN` are set in the environment, use those values and skip the rc file.
2. Otherwise read `~/.vcuprc` if it exists and merge: env vars override individual `url` / `token` fields from JSON.
3. If configuration is still incomplete, print an error with an example rc object and exit with code `1`.

<Steps>
<Step title="Set the server secret">
On Vercel, set `VCUP_TOKEN` to a long random string (README: any random secret). The deploy button wires this variable from `.env.example`.
</Step>
<Step title="Mirror the secret on the client">
Create `~/.vcuprc` with the same `token`, or export `VCUP_TOKEN` (and `VCUP_API_URL` if you rely on env-only config).
</Step>
<Step title="Verify upload auth">
Run `vcup <file>`. The CLI sends `Authorization: Bearer ${config.token}` to `${config.url}/api/upload`.
</Step>
</Steps>

Example rc file:

```json
{
  "url": "https://your-vcup-deployment.vercel.app",
  "token": "your-vcup-token"
}
```

Upload and delete requests from the CLI:

```ts
headers: {
  Authorization: `Bearer ${config.token}`,
  "X-Filename": filename,  // upload only
}
// delete also sets "X-Blob-Url": blobUrl
```

<Note>
`VCUP_TOKEN` names the same secret on server and client. The server never reads `~/.vcuprc`; only the CLI does.
</Note>

## Request flow (upload)

```mermaid
sequenceDiagram
  participant CLI as cli.ts
  participant API as api/upload.ts
  participant Blob as Vercel Blob

  CLI->>CLI: loadConfig() → token
  CLI->>API: POST /api/upload<br/>Authorization: Bearer token<br/>X-Filename
  API->>API: token === process.env.VCUP_TOKEN?
  alt mismatch or missing server VCUP_TOKEN
    API-->>CLI: 401 Unauthorized
  else authorized
    API->>Blob: put(filename, body, public)
    API-->>CLI: 200 { url, raw }
  end
```

Delete follows the same Bearer gate, then requires `X-Blob-Url`.

## Failure modes

| Symptom | Typical cause |
|---------|----------------|
| `401 Unauthorized` on upload/delete | Wrong token, missing `Authorization`, or `VCUP_TOKEN` not set on the server |
| CLI exits before request | Missing `~/.vcuprc` and incomplete env (`VCUP_API_URL` / `VCUP_TOKEN`) |
| Upload works but link is world-readable | Expected — proxy and raw URLs are not behind Bearer auth |

<Tip>
For local or CI uploads, export both `VCUP_API_URL` and `VCUP_TOKEN` so the CLI does not depend on a home-directory rc file.
</Tip>

## Operational checklist

- Generate a strong random `VCUP_TOKEN` for production; do not commit it.
- Set the identical value in Vercel env and client config.
- Rotate by updating server env and all clients; there is no grace period in code.
- Restrict who receives proxy or raw URLs if read access must stay private (vcup does not add download auth).

## Related pages

<CardGroup>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc` schema, env precedence, and matching client settings to your deployment.
</Card>
<Card title="Environment variables" href="/environment-variables">
Server `VCUP_TOKEN` and client `VCUP_API_URL` / `VCUP_TOKEN` reference.
</Card>
<Card title="API reference" href="/api-reference">
Upload, delete, and proxy endpoints with headers and status codes.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
401 responses, missing config, and other source-backed errors.
</Card>
</CardGroup>
