Access control with signed URLs

Signed delivery keeps CDN files private until your backend grants access. Use it when files should not be reachable by public CDN URL alone.

Prerequisites

To use signed URLs, enable them in your project settings under Delivery → CDN domain names → Enable signed URLs:

  1. Enable the secure subdomain for your project.
  2. Generate a signing secret. You can have up to two secrets at a time — the second is for rotation.
  3. Set up URL signing on your backend using the secret.
  4. Once your integration is ready, disable the public subdomain to close unsigned access.

Signed URLs also work on custom CNAMEs — contact support to configure a branded domain for signed delivery.

If your project has a legacy ucarecdn.com domain, you must disable it to fully close unsigned access. This action is irreversible — once disabled, the legacy domain cannot be re-enabled.

How it works

When a client requests a file from your secure subdomain or a custom CNAME configured for signed delivery, the CDN validates the token query parameter before serving the file. Your backend generates this token with your signing secret and appends it to the delivery URL before returning or redirecting the client to it.

If the token is missing, expired, or does not match the requested path, the CDN returns 403 Forbidden.

The CDN reads a token query parameter appended to your delivery URL. The token has three fields:

?token=exp={timestamp}~acl={acl}~hmac={digest}
FieldDescription
exp={timestamp}Unix timestamp (seconds) after which the token is invalid
acl={acl}Access Control List: the path or path pattern this token grants access to
hmac={digest}HMAC-SHA256 of exp={timestamp}~acl={acl}, keyed with your signing secret

ACL

The acl parameter defines which paths the token grants access to. It supports * as a suffix wildcard.

ACL valueAccess granted
/*Any file in the project
/{uuid}/Original file only
/{uuid}/*Original file and all its variants (transformations, adaptive video segments, etc.)
/{uuid}/-/resize/640x/One specific transformed version

Digest

The hmac field is a hex-encoded HMAC-SHA256 digest of the token body.

Token body is a string constructed from the expiry timestamp and ACL in this exact order:

exp={timestamp}~acl={acl}

To calculate value of the hmac field use the signing secret as the HMAC key to compute an HMAC-SHA256 digest of the token body.

Most integration failures happen when your backend signs a different byte sequence than the CDN validates:

  • Hex-decode the signing secret before using it as the HMAC key.
  • Sign the ACL exactly as it appears in the token body. Do not URL-encode it before calculating the digest.

Token playground

Token generation libraries

These libraries implement the same HMAC construction and produce compatible signed-delivery tokens:

File Uploader signing proxy

File Uploader loads image previews from CDN URLs. With signed delivery enabled, unsigned preview URLs return 403 Forbidden, and the browser cannot sign them because your signing secret must stay on your backend.

A signing proxy is your application endpoint that receives a File Uploader preview URL, checks whether the requester can access the file, signs the CDN path, and redirects to the signed CDN URL.

Use it with secureDeliveryProxy or secureDeliveryProxyUrlResolver File Uploader configuration options.

Typical flow:

File Uploader requests:
https://app.example.com/uc-preview?url=https%3A%2F%2F{subdomain}.ucarecd.net%2F{uuid}%2F
Your backend redirects to:
https://{subdomain}.s.ucarecd.net/{uuid}/?token=exp={timestamp}~acl={acl}~hmac={digest}

Your endpoint must:

  1. Validate that preview URL points to one of your Uploadcare CDN hosts.
  2. Check the requester’s credentials and access to the requested file or CDN path.
  3. Generate a time-limited token for the URL path.
  4. Redirect to the signed URL.

The example below shows the signing proxy pattern. Replace authenticateRequest and canViewUploadcareFile with your application’s own checks.

1import express from "express";
2import EdgeAuth from "akamai-edgeauth";
3
4const app = express();
5
6const secureCdnOrigin = new URL("https://{subdomain}.s.ucarecd.net");
7
8// File Uploader may pass either CDN host in the preview URL.
9const allowedPreviewHosts = new Set([
10 "{subdomain}.ucarecd.net",
11 "{subdomain}.s.ucarecd.net",
12]);
13
14const signingSecret = process.env.UPLOADCARE_SIGNING_SECRET;
15const durationSeconds = 500;
16const tokenName = "token";
17
18const edgeAuth = new EdgeAuth({
19 key: signingSecret,
20 windowSeconds: durationSeconds,
21 tokenName,
22 escapeEarly: true,
23});
24
25app.get("/uc-preview", async (req, res) => {
26 const user = await authenticateRequest(req);
27 if (!user) {
28 return res.sendStatus(401);
29 }
30
31 let previewUrl;
32 try {
33 previewUrl = new URL(String(req.query.url || ""));
34 } catch {
35 return res.status(400).send("Invalid preview URL");
36 }
37
38 if (previewUrl.protocol !== "https:" || !allowedPreviewHosts.has(previewUrl.hostname)) {
39 return res.status(400).send("Unsupported preview URL");
40 }
41
42 if (!(await canViewUploadcareFile(user, previewUrl.pathname))) {
43 return res.sendStatus(403);
44 }
45
46 const token = edgeAuth.generateACLToken(previewUrl.pathname);
47 const signedUrl = new URL(previewUrl.pathname + previewUrl.search, secureCdnOrigin);
48
49 signedUrl.searchParams.set(tokenName, token);
50
51 res.redirect(signedUrl.toString());
52});
53
54async function authenticateRequest(req) {
55 // Authenticate the request and return the user object.
56 return null;
57}
58
59async function canViewUploadcareFile(user, pathname) {
60 // Check that `user` can access the file UUID or CDN path in `pathname`.
61 return false;
62}
63
64app.listen(3000);

Test environment

Use these credentials to test token generation without production keys:

  • Hostname: sectest.ucarecdn.com
  • Secret: 73636b61519adede42191efe1e73f02a67c7b692e3765f90c250c230be095211

Do not use these credentials in production. This secret is publicly known.

Billing

Signed URLs are available on all plans, including free.