A signed URL is a time-limited, pre-authorized URL that allows users to access a protected resource (such as an image, video, or document) without exposing the underlying storage system or requiring direct authentication.
These URLs contain a cryptographic signature and optional metadata (like expiration time and access permissions). Only users with a valid signed URL can access the resource.
How it Works
A user (or client) requests access to a protected resource.
The server/service generates a signed URL using a private key or a secret.
The user can upload, download, or view (can be protected to only download) the resource by using the URL until it expires.
Once the URL expires, access is revoked.
Common Use Cases of Signed URLs
Secure File Uploads: Allow clients to upload files directly to storage without exposing backend systems.
Secure File Downloads: Allow users to download resources securely without making them public.
Access Control: Grant temporary access to internal or confidential resources.
Share AWS S3 Object using Signed URL
Lets try to share image using signed URL in AWS S3.
Here we will use localstack, a cloud service simulator that works locally in our machine to simulate AWS S3 stack. To install localstack you can follow the guide in .
Start Localstack
Start your localstack using optional LOCALSTACK_S3_SKIP_SIGNATURE_VALIDATION=0 to enable validation of S3 pre-signed URL request signature.
Open the URL in your browser and it should start download the image. The URL will expired in 3600s or ypu can override it by adding --expires-in options.
When you try to access the URL after expired it will return an error AccessDenied: Request has expired.
Most of the time you will not use the AWS CLI to generate a signed URL. Here are example on how to create a signed URL using AWS SDK connected to localstack in Golang.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func main() {
cfg := aws.Config{
Region: "ap-southeast-1", // any region will work for localstack
Credentials: credentials.NewStaticCredentialsProvider("test", "test", "test"),
}
s3client := s3.NewFromConfig(cfg, func(o *s3.Options) {
o.BaseEndpoint = aws.String("http://localhost:4566")
o.UsePathStyle = true
})
psClient := s3.NewPresignClient(s3client)
req, err := psClient.PresignGetObject(context.TODO(), &s3.GetObjectInput{
Bucket: aws.String("my-bucket"),
Key: aws.String("image.png"),
}, s3.WithPresignExpires(5*time.Minute))
if err != nil {
log.Fatal(err)
}
fmt.Println(req.URL)
}
➜ go run main.go
http://localhost:4566/my-bucket/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test%2F20250303%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Date=20250303T073210Z&X-Amz-Expires=300&X-Amz-Security-Token=test&X-Amz-SignedHeaders=host&x-id=GetObject&X-Amz-Signature=07782ba9ccc584bc5f85fd6b41c19cf79024626ce96830eaf4c12783d23455dd