Serve private files, images and videos only to the people you choose. Your server signs a URL that stops working after a time you set, and Fileboy refuses everything else.
Every app is public by default: anyone with the URL of a file, image or video can read it. There are two ways to need a signature:
A read that needs a signature is allowed with one of:
fb_expfb_sigAuthorization: token ...Signing happens on your server and needs no request to Fileboy. The private key never goes to the browser, only the finished URL does.
Signatures are made the same way in both cases, with the app's private key. A signature on a public file of a public app is simply ignored, so you can sign every URL you hand out without checking which files need it.
Give the SDK the URL of any file, image or transform and an expiry (an hour if you do not say). You get the same URL back with the signature added. It works for
/files/get/images/getPUBLIC_KEY+labelhttps://fileboy.io/f/FILE_ID// on your server, with the private key
const fileboy = Fileboy({ publicKey, privateKey })
// any file URL, image URL or transform URL. Expires in 10 minutes.
const url = fileboy.signUrl(
fileboy.buildImageUrl(id, { transforms: [{ type: "resize", width: 400, height: 400 }] }),
{ expiresIn: 600 },
)
// https://api.fileboy.io/images/get/2a86...?transform=resize:400x400&fb_exp=1800000000&fb_sig=...
// only this address can use it
const locked = fileboy.signUrl(fileboy.buildFileUrl(id), { expiresIn: 60, ip: "203.0.113.7" })
// buildFileUrl and buildImageUrl add a short-lived token to the URL when the client has a
// private key. signUrl removes it: a signed URL does not need it.# on your server, with the private key
url = fileboy.sign_url(fileboy.build_image_url(id, transforms: { resize: "400x400" }), expires_in: 600)
# https://api.fileboy.io/images/get/2a86...?transform=resize:400x400&fb_exp=1800000000&fb_sig=...
# only this address can use it
locked = fileboy.sign_url(fileboy.build_file_url(id), expires_in: 60, ip: "203.0.113.7")# on your server, with the private key
url = client.sign_url(client.build_image_url(file_id, transforms={"resize": "400x400"}), expires_in=600)
# https://api.fileboy.io/images/get/2a86...?transform=resize:400x400&fb_exp=1800000000&fb_sig=...
# only this address can use it
locked = client.sign_url(client.build_file_url(file_id), expires_in=60, ip="203.0.113.7")// on your server, with the private key
$url = $fileboy->signUrl($fileboy->buildImageUrl($id, ['resize' => '400x400']), expiresIn: 600);
// https://api.fileboy.io/images/get/2a86...?transform=resize:400x400&fb_exp=1800000000&fb_sig=...
// only this address can use it
$locked = $fileboy->signUrl($fileboy->buildFileUrl($id), expiresIn: 60, ip: '203.0.113.7');# Any language can sign: HMAC-SHA256 of the canonical string, keyed with your private key
ID=2a860257-ecf9-424a-846e-00ab68e2fd58
EXP=$(( $(date +%s) + 600 ))
CANON=$(printf 'FBS1\nscope=file\nid=%s\ntransform=\nfilename=\nexp=%s\nip=' "$ID" "$EXP")
SIG=$(printf '%s' "$CANON" | openssl dgst -sha256 -hmac "$PRIVATE_KEY" -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
curl "https://api.fileboy.io/files/get/$ID?fb_exp=$EXP&fb_sig=$SIG"A signature is bound to what you signed: the file, the exact
transformdownloadA video is many requests: the manifest, a playlist per quality, the segments, the poster and the captions, and a player builds most of those URLs itself. So one signature covers everything of one video. Fileboy passes it on inside the HLS and DASH manifests, so the player keeps working with the single query string you give it.
// on your server. One signature covers the manifest, segments, poster and captions.
// Make it last longer than the video takes to watch.
const signedQuery = fileboy.signVideo(videoId, { expiresIn: 4 * 3600 })
// in the page: <fileboy-player video-id="${videoId}" signed-query="${signedQuery}"></fileboy-player>
// or any video URL: `https://api.fileboy.io/videos/${videoId}/manifest?${signedQuery}`# on your server. One signature covers the manifest, segments, poster and captions.
signed_query = fileboy.sign_video(video_id, expires_in: 4 * 3600)
manifest_url = "https://api.fileboy.io/videos/#{video_id}/manifest?#{signed_query}"# on your server. One signature covers the manifest, segments, poster and captions.
signed_query = client.sign_video(video_id, expires_in=4 * 3600)
manifest_url = f"https://api.fileboy.io/videos/{video_id}/manifest?{signed_query}"// on your server. One signature covers the manifest, segments, poster and captions.
$signedQuery = $fileboy->signVideo($videoId, expiresIn: 4 * 3600);
$manifestUrl = "https://api.fileboy.io/videos/{$videoId}/manifest?{$signedQuery}";With
<fileboy-player>signed-queryFor a private app the manifests never point at the public CDN: every segment is served through Fileboy, where the signature is checked, and counts towards your transfer like any other read.
In the dashboard it is Settings, Read access. From code it needs the private key:
// needs the private key. Every unsigned link stops working at once.
await fileboy.setRequireSignedReads(true)
await fileboy.getRequireSignedReads() // true# needs the private key. Every unsigned link stops working at once.
fileboy.update_require_signed_reads(true)
fileboy.fetch_require_signed_reads # => true# needs the private key. Every unsigned link stops working at once.
client.set_require_signed_reads(True)
client.get_require_signed_reads() # True// needs the private key. Every unsigned link stops working at once.
$fileboy->setRequireSignedReads(true);
$fileboy->getRequireSignedReads(); // truecurl -X PATCH https://api.fileboy.io/app/settings \
-H "Authorization: token YOUR_SIGNED_TOKEN" \
-H "Content-Type: application/json" \
-d '{"requireSignedReads": true}'See the API reference for
GET /app/settingsPATCH /app/settingsPass
ip203.0.113.0/24In an app that requires signed URLs the answer is the same whether or not the file exists, so unsigned requests cannot be used to find out what you store. (In a public app, where a public file or label already answers anyone, a label that does not exist is a 404 and one on a Signed URLs only file is a 401.) Refusals are never cached.
401 signature_required403 signature_invalid403 signature_expired403 ip_not_allowed404Private responses are sent with
Cache-Control: private, max-age=Nfb_sigSignatures are made with the app's private key. Regenerating the private key on the Keys page stops every signed URL made with the old key straight away, which is what you want after a leak. To roll the key without breaking pages that are already open, choose Rotate instead: signed URLs made with the old key keep working for 7 days while you deploy the new key. The old key can only verify signatures, it cannot manage files. Regenerating the public key changes label URLs, which include it.
The signature is the base64url (no padding) HMAC-SHA256, keyed with the private key, of this text, with a newline between lines and none at the end:
1 2 3 4 5 6 7FBS1 scope=file id=2a860257-ecf9-424a-846e-00ab68e2fd58 transform=resize%3A100x100 filename= exp=1800000000 ip=
scopefile/files/get/images/getfile-data/files/datavideo/videos/ididPUBLIC_KEY+labeltransformfilenametransformexpipA-Z a-z 0-9 - . _ ~Add
fb_expfb_ipfb_sigopensslauthorization