

0 / 2 embers
0 / 3000 xp
click for more info
Complete a lesson to start your streak
click for more info
Still calibrating
click for more info
Not enough gems
Cost: 6 gems
1: Principle of Least Privilege
incomplete
2: Preventing Broken Access Control
incomplete
3: Don't Trust the Client
incomplete
4: Access Control Models
incomplete
5: Attribute-Based Access Control
incomplete
6: RBAC vs. ABAC
incomplete
7: Insecure Direct Object References
incomplete
8: Securing File Downloads
incomplete
9: Signed URLs
incomplete
Back
ctrl+,
Next
ctrl+.
This lesson's interactive features are locked, please to keep using them
A signed URL contains a temporary bearer credential. Anyone who has the complete URL can use it to access a resource until it expires, even without a session cookie.
That makes signed URLs convenient for handing an authorized download to a file server or storage service... but it also means the URL must be short-lived and tightly scoped.
Signed URLs typically follow this flow:
A signed URL contains the resource path, an expiration timestamp, and a signature. With dummy values, the complete URL looks like this:
http://localhost:3000/files/1/signed-download?expires=1300&signature=15a4b83da8db92f7d83ac2684ebe7d1a98108b165d4d18fbae94098ae291e5f2
/files/1/signed-download identifies the exact resource and action.expires=1300 is an illustrative Unix timestamp.signature=... is a 64-character hexadecimal HMAC over the method, path, and timestamp.The signature is a Hash-based Message Authentication Code (HMAC), created with createHmac:
const payload = `GET\n/files/${fileId}/signed-download\n${expires}`;
const signature = createHmac("sha256", signingKey)
.update(payload)
.digest("hex");
The \n characters separate fields in the payload being signed. They do not appear in the URL. The secret key turns that payload into a signature, so changing the file ID, path, or expiration invalidates it.
The signed endpoint, /files/1/signed-download in this case, should reject malformed or expired timestamps, recompute the expected HMAC, and compare signatures with timingSafeEqual:
if (expires <= now || !timingSafeEqual(expectedSignature, providedSignature)) {
res.status(403).send("Download Link Unavailable");
return;
}
This verification step is what prevents an attacker from tampering with the URL. If an attacker manually changes the expiration in the URL, the signature will no longer match and the request will be rejected.
Keep expiration short! Signed URLs are bearer credentials, so if an attacker gets hold of a signed URL, they can use it until it expires. Five minutes is a reasonable limit for many use cases.
Bearly Secure already issues five-minute, file-scoped signed URLs, but its signing helper is deliberately incomplete. Use Node's crypto APIs to sign and verify each URL.
GET\n/files/FILE_ID/signed-download\nEXPIRES
With Bearly Secure still running, run and submit the CLI tests from the project root.