Skip to main content

Presigning and query authentication

Providers authenticate requests in two places. Headers are the default: the signature travels with the request and dies with it. The query string embeds authentication in the URI itself, so whoever holds the URL can perform exactly that request with no credentials of their own — that is what "presigned URLs" are.

// Header signing:
signer.sign(&mut req, None).await?;

// Query authentication, valid for 1 hour (where supported):
signer.sign(&mut req, Some(Duration::from_secs(3600))).await?;
SituationMode
Your service makes the request itselfHeader signing
A browser, another service, or a user uploads/downloads directlyQuery authentication
The credential itself is already scoped (SAS)Query by nature

Produce a presigned URL​

use reqsign::aws;
use std::time::Duration;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let signer = aws::default_signer("s3", "us-east-1");
let mut req = http::Request::get("https://s3.amazonaws.com/my-bucket/report.csv")
.body(())?
.into_parts()
.0;
signer
.sign(&mut req, Some(Duration::from_secs(3600)))
.await?;
// Share req.uri over a trusted channel; it contains authentication material.
assert!(req.uri.query().is_some());
Ok(())
}

For AWS this produces X-Amz-Algorithm, X-Amz-Credential, X-Amz-Expires, X-Amz-Signature, and companions in the query string. Other providers use their own parameter sets; the shape of the call is identical.

expires_in is service-specific​

Passing Some(duration) does not universally mean "presign" — the configured service signer and credential type determine how the duration is interpreted:

  • AWS SigV4/SigV4a, Aliyun OSS, Tencent COS, Volcengine TOS, Google Cloud Storage, and Huawei Cloud OBS select query authentication with that validity window.
  • Azure Storage SAS credentials authenticate through the query string by nature; shared keys and bearer tokens sign headers.
  • Oracle Cloud bounds credential validity but produces no query authentication — header signing is its only mode.

The provider matrix carries the authoritative header/query flags, and the expiration contract states the exact semantics.

Before you ship one​

  • The method and URI are part of the signature. A URL presigned for GET /report.csv authorizes exactly that — not PUT, not another key.
  • Credential validity must cover the window. The signer refreshes a cached credential that would expire before expires_in elapses, and errors if no credential can cover the window — a URL that dies early is a bug, not a surprise. See Credential freshness.
  • Anyone with the URL is authorized. Treat presigned URLs as secrets with a TTL: transmit over TLS, scope tightly, keep windows short.
  • Never re-encode a presigned URI. The signature covers the encoded bytes; a second encoding pass breaks it. See Wire-ready URIs.
  • Tighter blast radius: grant a downscoped credential first and presign with that.