Upload control with signed uploads
Signed uploads restrict who can upload files to your Uploadcare project, and for how long. Your backend issues a short-lived credential, the client sends it with every upload, and Upload API rejects anything without a valid one.
What gets signed
Two project settings require a credential. Signed Uploads is off until you turn it on. Signed AI image generation is on by default for every project, so the AI Image Editor needs an auth token even when uploads are unsigned:
Turn on Signed Uploads last, once your clients send a credential. You can turn Signed AI image generation off, but then anyone with your public key can spend your project’s generations.
Two kinds of credential
Both are generated on your backend:
- Auth token (recommended). A short-lived JWT sent in the
Authorizationheader. You can limit it to particular endpoints (its scope) and to a number of operations, one operation being one uploaded file or one AI generation or edit. Both are covered in scope and operation limits. Without limits, a token works for every protected endpoint, any number of times, until it expires. - Signature. The legacy scheme: a
signatureandexpirepair sent with the request, with no endpoint or operation limits. Still supported, and still the only option for the deprecated jQuery File Uploader.
The Upload API authentication reference has the wire format for both: the claims, each endpoint’s scope path and operation cost, and the error codes.
Issue tokens from your backend
Before you start:
- Create a secret key in your project’s API keys and keep it on the server, for example in
UPLOADCARE_SECRET_KEY. Never send it to the browser. - In Node, install the SDK:
npm install @uploadcare/signed-uploads. Use 6.23.0 or later, which also ships the browser-sideAuthTokenCache.
Expose an endpoint that checks who is asking and returns a fresh token:
The client gets back { "token": "eyJhbGciOiJIUzI1NiIs…" }.
For other languages, use any HS256 JWT library. The reference has samples for Python and Ruby.
Send the token from your client
Every client follows the same two rules:
- Leaving
authTokenunset (null,undefinedor'') sends requests without a token. - A token function that returns nothing (
null,undefinedor'') fails the request with anAuthTokenResolverError, the same as one that throws. LeaveauthTokenunset for unsigned requests rather than return an empty value.
authToken takes three shapes, and the shape decides how much the library can do on its own:
invalidateAuthToken()
call.Each client caches and refreshes the token its own way:
- File Uploader 1.35.1 or later. Set
authTokento a function that fetches the token, and the uploader caches and refreshes it. See signed uploads. - Upload Client 6.23.0 or later. Pass
authToken, and in the browser wrap your fetch inAuthTokenCache. See signed uploads. - AI Image Editor 0.2.6 or later. Set
authToken. Inside File Uploader it uses the uploader’s token, so you set nothing. See Signed AI image generation. - Upload API directly. Send
Authorization: Bearer <token>, as in the reference’s example.
authToken ignore it without a warning, so the request goes out unsigned and fails with SignatureRequiredError.The cache is in memory
Every one of these caches lives in the page. Nothing is written to
localStorage, a cookie or anywhere else, so a token never outlives the tab
that fetched it. A reload, a second tab, and a second uploader or editor
instance each start empty and call your function again. The one way to end up
with no cache at all is the AI Image Editor’s
cacheAuthToken: false,
which calls your function for every request and leaves nothing to invalidate.
Each cache watches the clock and nothing else. It replaces a token it can see expiring, but it cannot tell that a token has spent its last operation, so a spent one keeps going out until something drops it.
That decides when you need invalidateAuthToken(),
or invalidate() with AuthTokenCache:
Turn it on
- Go to your Dashboard and select the project.
- In the uploading settings, turn on Signed Uploads.
Signed AI image generation, right below it, is already on, so the AI Image Editor needs a token whether or not you do this.
When a credential is refused
Uploadcare refused it. Upload API answers with an error_code: AccessTokenInvalidError or AccessTokenExpiredError (401), ScopeForbiddenError or OperationsLimitExceededError (403), or SignatureRequiredError (400) when an upload request carried no credential. AI image requests without a token get AccessTokenInvalidError instead. A refused token is not always your problem to solve: on an upload, when the client holds a token function, an expired token and a spent operation limit are each retried once with a freshly fetched token, so the error only reaches you if the second token is refused too. ScopeForbiddenError is never retried, since a reissued token carries the same scope. AI image requests are never retried either, whatever the code. The reference has the cause of each.
Your token function failed. No request is sent, so there is no error_code. The JavaScript libraries wrap what your function threw in an AuthTokenResolverError, and nothing is cached or retried. To tell failures apart, include your endpoint’s HTTP status in what the function throws: a 401 means the session is gone and the user has to sign in again, while a 500 or a network error is worth retrying.
Troubleshooting
Moving from signatures
Signatures keep working against the same project setting, so you can move a piece at a time.
- Requests you build yourself can move gradually. Upload API checks a token only when a request carries no
signature, so a request sending both is checked the old way. Start sending tokens alongside signatures, then drop the signature. - File Uploader switches all at once. Setting
authTokenmakes it send the token and ignoresecureSignature,secureExpireandsecureUploadsSignatureResolver, with a console warning if you set both.
What you gain: a token can be limited to particular endpoints and a number of operations, and it travels in a header instead of request parameters. A signature only carries an expiry.