Add Vercel OIDC auth and Presigned URLs#1056
Merged
Merged
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 9d6982a The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
1ad2b18 to
e7dff18
Compare
f890e7c to
179061a
Compare
falcoagustin
added a commit
to vercel/vercel
that referenced
this pull request
Apr 30, 2026
vercel/storage#1056 makes the SDK's `options.token` read-write only (parsed via underscore split for storeId). Passing the OIDC JWT through it produces a malformed storeId. Instead pass only `storeId` and rely on `getVercelOidcToken()` reading `VERCEL_OIDC_TOKEN` from `process.env`; our resolver already hoists `.env.local` values there. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BLOB_STORE_ID and the storeId option are accepted in either store_<id> or <id> form (Vercel env pull writes the prefixed form), and may be mixed-case. resolveBlobAuth was passing those through verbatim, so the storeId in API headers and CDN host subdomains could be malformed — e.g. blob.get against a private store with `store_WdsHBk1w9fDO4vPW` built `https://store_WdsHBk1w9fDO4vPW.private.blob.vercel-storage.com/...` and 404'd. The RW path was unaffected because parseStoreIdFromReadWriteToken yields a bare lowercase id from the token's structure. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first pass also lowercased — that breaks API requests, since the Vercel Blob API is case-sensitive on the storeId (header and bearer parsing). The CDN host accepts either case, so prefix-strip alone is sufficient and works for both consumers. Verified end-to-end against a private store: blob get and blob list both succeed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 tasks
* presigned urls * up * normalize * fix * update to new signing string * [wip] presigned url version of handleUpload (#1059) * [wip] presigned url version of handleUpload * up * up * up * add presigned urls for mpu * update options shape * webhook signature * update example * up * up * up * change to operation * up * up * verify webhook signature * up * callbackUrl * non-null * presigned url opts * new delegation token + url opts * dont lowercase store id * validUntil instead of ttlSeconds * up * up * Add 'delete' to DelegationOperation for presigned DELETE URLs (#1061) Mirrors the API-side change that adds `delete` to the issue_signed_token allowed operations. `presignUrl` now signs canonical `operation=delete\npathname=...` against the public blob object URL (same host shape as `get`/`head`, just used with HTTP DELETE). Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * change upload to put * delete * up * presigned url payload * update app * fix * docs * refactor presign url * up * up * up * add head * up * add params to url * add keys first * token payload * add buildPresignedGetUrl * up * presignUrl returns url, not accepted in sdk methods * up * up * feat(blob): presigned HEAD & DELETE URLs (#1064) * feat(blob): presigned DELETE URLs Wires the `'delete'` DelegationOperation (added in #1061, since rebased out of elliot/presigned-urls) through `presignUrl`. A token issued with `operations: ['delete']` can now mint a presigned `DELETE /?pathname=…` against the control-plane API. - `PresignDeleteUrlOptions` accepts `pathname`, optional `validUntil`, optional `ifMatch`. Upload-only fields are rejected at the type level. - `presign()` gates on the delegation scope including `'delete'`. - `buildPresignedDeleteUrl()` mirrors the PUT URL shape; the HTTP method is the discriminator (canonical signing string carries `operation=delete`). - `buildPresignCanonicalQueryEntries()` for `delete` emits only `validUntil` (when below the delegation ceiling) and `ifMatch`. - E2E test route + delete button on the presigned-upload demo page. Based on `elliot/presigned-urls`, not `main`. * feat(blob): presigned HEAD URLs (#1065) `HEAD` mirrors `GET` against the blob object host (`<storeId>.<access>.blob.vercel-storage.com/<pathname>`); the URL shape is identical and the HTTP method is the discriminator. `operation=head` goes into the canonical signing string so a GET-signed URL cannot be replayed as a HEAD (and vice versa). - `'head'` added to `DelegationOperation`. - `PresignHeadUrlOptions` — same shape as `PresignGetUrlOptions` (`pathname`, optional `validUntil`). - `presign()` gates on the delegation scope including `'head'`. - `presignUrl()` reuses `buildPresignedGetUrl()` for `operation: 'head'`. - `buildPresignCanonicalQueryEntries()` for `head` emits only `validUntil` (when below the delegation ceiling) — same as `get`. - E2E test route + HEAD button on the presigned-upload demo page. Stacked on `falcoagustin/presigned-delete-impl` (#1064). * environment error * up * cleanup --------- Co-authored-by: Agustin Falco <agusfalco_11@hotmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Collaborator
falcoagustin
left a comment
There was a problem hiding this comment.
A few real bugs worth fixing before merging — inline.
falcoagustin
approved these changes
May 18, 2026
Collaborator
falcoagustin
left a comment
There was a problem hiding this comment.
LGTM — all three findings from the prior round are resolved.
uploadPresigned.extraChecksnow correctly rejectsallowOverwriteand the error message names the right function.publicBlobObjectUrlremoved, so the case-folding footgun has no caller.- Explicit
oidcTokenwithout a resolvablestoreIdnow throws instead of silently downgrading toBLOB_READ_WRITE_TOKEN.
Minor nits to consider in a follow-up (non-blocking):
packages/blob/src/signed-token.ts:248-252— the local lowercasingnormalizeStoreIdis unreferenced now; worth deleting so it can't be picked back up by accident.- Add a test for the new
oidcToken-without-storeIdthrow branch inhelpers.ts.
falcoagustin
added a commit
to vercel/vercel
that referenced
this pull request
May 20, 2026
# Problem `vercel blob` only authenticates via `BLOB_READ_WRITE_TOKEN` (flag, env, or `.env.local`). With OIDC landing in `@vercel/blob`, the CLI needs to: 1. Speak OIDC at all (env-pair already in `.env.local` from `vercel env pull`). 2. Let users pass OIDC creds explicitly without env state, the way `--rw-token` already works. 3. Refuse to silently downgrade identity when OIDC config is half-set — the AWS credential-provider posture, not the "fall through to whatever works" one. # Solution `getBlobRWToken` returns a discriminated union (`kind: 'rw' | 'oidc'`) and resolves in this order: 1. **Explicit flags (exclusive):** - `--rw-token <token>` → RW. - `--oidc-token <jwt> --store-id <id>` → OIDC. Both required when either is given; partial → hard error. 2. **Env-derived auth.** `process.env` ∪ `.env.local` (process.env wins; `.env.local` fills gaps), then: - **Partial OIDC (`VERCEL_OIDC_TOKEN` xor `BLOB_STORE_ID`) → hard error.** No silent fall-through to RW. Mirrors the explicit-flag policy and AWS's behavior when `AWS_WEB_IDENTITY_TOKEN_FILE` is set without `AWS_ROLE_ARN`. - Both OIDC vars → OIDC (hoisted onto `process.env` so the SDK's `getVercelOidcToken()` finds them). - `BLOB_READ_WRITE_TOKEN` → RW. - Otherwise → no-creds error. Two helpers consume the union: `blobOpts(auth)` builds the SDK option object (`{ token }` for RW, `{ storeId }` for OIDC — never passes the OIDC JWT through `options.token`, which would be parsed as an RW token), and `getStoreIdFromAuth(auth)` resolves `store_<id>` for store-management subcommands. `findFlagValue(argv, flag)` is a small linear scanner that reads the three auth flags directly, avoiding a duplicate `parseArguments` over the whole argv. The resolver no longer needs to know about argv layout, flag specifications, or `permissive: true`. # What's different from the previous priority list | Before | After | |---|---| | `--rw-token` only explicit-flag path | `--rw-token` + `--oidc-token` / `--store-id` pair | | Env vars resolved per-source (process.env, then `.env.local`), each requiring both OIDC vars in the same source | Sources combined; vars can be split (e.g. token in shell, store id in `.env.local`) | | Partial OIDC config silently fell back to RW (identity confusion) | Partial OIDC hard-errors with explicit message | | Resolver re-ran `parseArguments` to read three known flags | Resolver scans argv directly for the three flags | # Dependency Bumps `@vercel/blob` to the snapshot containing vercel/storage#1056 (OIDC support + `store_` prefix normalization in `resolveBlobAuth`). Without that, `blob get` against a private store via OIDC would still 404 because the SDK would build a CDN host with `store_…` in the subdomain. # Test plan - [x] `vercel blob list/get` with `--oidc-token` + `--store-id` from a directory with no `.env.local` — succeeds. - [x] `--oidc-token` alone or `--store-id` alone → partial-flags hard error. - [x] `.env.local` with `VERCEL_OIDC_TOKEN` set but `BLOB_STORE_ID` missing, plus a working `BLOB_READ_WRITE_TOKEN` — used to silently use RW; now hard-errors. - [x] `.env.local` with only `BLOB_READ_WRITE_TOKEN` (no OIDC vars at all) — RW path still works. - [x] Both OIDC vars present in `.env.local` — OIDC path works, including `blob get` against a private store (paired with the SDK fix above). - [x] `--rw-token` flag — unchanged behavior, still wins over env. - [x] Equals form for the new flags (`--oidc-token=… --store-id=…`) — works. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Jeff See <jeffsee.55@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Adds OIDC auth for all methods in the SDK and adds support for Presigned Urls.
Adds a new
uploadPresignedandhandleUploadPresignedthat used presigned urls for client uploads, which is needed as read-write tokens are removed.Minor version bump.