Presigned URLs allow you to grant temporary access to S3 objects without requiring AWS credentials. The AWS SDK for Go v2 S3 package provides comprehensive support for generating presigned URLs for various operations.
Presigned URLs are signed URLs that provide temporary access to S3 resources. They include authentication information in the URL query string, allowing clients to perform S3 operations without AWS credentials.
Key concepts:
func NewPresignClient(c *Client, optFns ...func(*PresignOptions)) *PresignClientCreates a new presign client for generating presigned URLs.
Parameters:
c - The S3 client to use for presigning operationsoptFns - Optional configuration functions to customize presign behaviorReturns:
*PresignClient - A presign client configured with the provided optionsExample:
import (
"context"
"time"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
// Load AWS configuration
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
panic(err)
}
// Create S3 client
client := s3.NewFromConfig(cfg)
// Create presign client with default 1-hour expiration
presignClient := s3.NewPresignClient(client, func(po *s3.PresignOptions) {
po.Expires = time.Hour
})type PresignOptions struct {
// ClientOptions are list of functional options to mutate client options used by
// the presign client.
ClientOptions []func(*Options)
// Presigner is the presigner used by the presign url client
Presigner HTTPPresignerV4
// Expires sets the expiration duration for the generated presign url. This should
// be the duration in seconds the presigned URL should be considered valid for. If
// not set or set to zero, presign url would default to expire after 900 seconds.
Expires time.Duration
// presignerV4a is the presigner used by the presign url client
presignerV4a httpPresignerV4a
}Configuration options for presigning operations.
Fields:
ClientOptions - []func(*Options)
List of functional options to configure the underlying S3 client used for presigning. Applied when creating presigned URLs.
Presigner - HTTPPresignerV4
The SigV4 presigner implementation. If not provided, a default presigner is created automatically.
Expires - time.Duration
Duration for which the presigned URL remains valid. Default is 15 minutes (900 seconds). Maximum allowed is 7 days for SigV4.
presignerV4a - httpPresignerV4a (internal)
SigV4A presigner for multi-region access points. Created automatically if needed.
func WithPresignClientFromClientOptions(optFns ...func(*Options)) func(*PresignOptions)Helper function to apply S3 client options to presign operations.
Parameters:
optFns - S3 client option functions to applyReturns:
PresignOptionsExample:
// Create presign client with custom endpoint
presignClient := s3.NewPresignClient(
client,
s3.WithPresignClientFromClientOptions(func(o *s3.Options) {
o.UsePathStyle = true
o.BaseEndpoint = aws.String("https://custom-s3-endpoint.com")
}),
)func WithPresignExpires(dur time.Duration) func(*PresignOptions)Helper function to set the expiration duration for presigned URLs.
Parameters:
dur - Duration for which the presigned URL should be validReturns:
PresignOptionsExample:
// Generate presigned URL valid for 2 hours
presignedReq, err := presignClient.PresignGetObject(
ctx,
&s3.GetObjectInput{
Bucket: aws.String("my-bucket"),
Key: aws.String("my-key"),
},
s3.WithPresignExpires(2*time.Hour),
)func WithSigV4SigningName(name string) func(*Options)Overrides the SigV4 signing service name. Rarely needed for standard S3 operations.
func WithSigV4SigningRegion(region string) func(*Options)Overrides the SigV4 signing region. Rarely needed for standard S3 operations.
func WithSigV4ASigningRegions(regions []string) func(*Options)Sets regions for SigV4A multi-region signing, used with Multi-Region Access Points.
func (c *PresignClient) PresignGetObject(
ctx context.Context,
params *GetObjectInput,
optFns ...func(*PresignOptions),
) (*v4.PresignedHTTPRequest, error)Generates a presigned URL for downloading an object from S3.
Parameters:
ctx - Context for the requestparams - GetObject input parameters (bucket, key, version, etc.)optFns - Optional presign configuration functionsReturns:
*v4.PresignedHTTPRequest - Presigned request containing URL and headerserror - Any error encountered during presigningExample:
import (
"context"
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func generateDownloadURL(bucket, key string) (string, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return "", err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
// Generate presigned GET URL valid for 15 minutes
presignedReq, err := presignClient.PresignGetObject(
context.TODO(),
&s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
},
s3.WithPresignExpires(15*time.Minute),
)
if err != nil {
return "", err
}
fmt.Println("Presigned URL:", presignedReq.URL)
fmt.Println("HTTP Method:", presignedReq.Method)
return presignedReq.URL, nil
}Use cases:
func (c *PresignClient) PresignPutObject(
ctx context.Context,
params *PutObjectInput,
optFns ...func(*PresignOptions),
) (*v4.PresignedHTTPRequest, error)Generates a presigned URL for uploading an object to S3.
Parameters:
ctx - Context for the requestparams - PutObject input parameters (bucket, key, metadata, etc.)optFns - Optional presign configuration functionsReturns:
*v4.PresignedHTTPRequest - Presigned request containing URL and headerserror - Any error encountered during presigningExample:
func generateUploadURL(bucket, key string) (string, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return "", err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
// Generate presigned PUT URL
presignedReq, err := presignClient.PresignPutObject(
context.TODO(),
&s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
},
s3.WithPresignExpires(30*time.Minute),
)
if err != nil {
return "", err
}
fmt.Println("Upload URL:", presignedReq.URL)
fmt.Println("Use HTTP PUT to upload to this URL")
// Client would use: PUT presignedReq.URL with file data
return presignedReq.URL, nil
}Use cases:
func (c *PresignClient) PresignDeleteObject(
ctx context.Context,
params *DeleteObjectInput,
optFns ...func(*PresignOptions),
) (*v4.PresignedHTTPRequest, error)Generates a presigned URL for deleting an object from S3.
Parameters:
ctx - Context for the requestparams - DeleteObject input parameters (bucket, key, version, etc.)optFns - Optional presign configuration functionsReturns:
*v4.PresignedHTTPRequest - Presigned request containing URL and headerserror - Any error encountered during presigningExample:
func generateDeleteURL(bucket, key string) (string, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return "", err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
// Generate presigned DELETE URL
presignedReq, err := presignClient.PresignDeleteObject(
context.TODO(),
&s3.DeleteObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
},
)
if err != nil {
return "", err
}
fmt.Println("Delete URL:", presignedReq.URL)
fmt.Println("Use HTTP DELETE to remove this object")
return presignedReq.URL, nil
}func (c *PresignClient) PresignHeadObject(
ctx context.Context,
params *HeadObjectInput,
optFns ...func(*PresignOptions),
) (*v4.PresignedHTTPRequest, error)Generates a presigned URL for retrieving object metadata without downloading the object body.
Parameters:
ctx - Context for the requestparams - HeadObject input parameters (bucket, key, version, etc.)optFns - Optional presign configuration functionsReturns:
*v4.PresignedHTTPRequest - Presigned request containing URL and headerserror - Any error encountered during presigningExample:
func generateMetadataURL(bucket, key string) (string, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return "", err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
// Generate presigned HEAD URL to check if object exists
presignedReq, err := presignClient.PresignHeadObject(
context.TODO(),
&s3.HeadObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
},
)
if err != nil {
return "", err
}
fmt.Println("Metadata URL:", presignedReq.URL)
fmt.Println("Use HTTP HEAD to get object metadata")
return presignedReq.URL, nil
}Use cases:
func (c *PresignClient) PresignDeleteBucket(
ctx context.Context,
params *DeleteBucketInput,
optFns ...func(*PresignOptions),
) (*v4.PresignedHTTPRequest, error)Generates a presigned URL for deleting a bucket.
Parameters:
ctx - Context for the requestparams - DeleteBucket input parameters (bucket name, expected owner, etc.)optFns - Optional presign configuration functionsReturns:
*v4.PresignedHTTPRequest - Presigned request containing URL and headerserror - Any error encountered during presigningExample:
import (
"context"
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func generateDeleteBucketURL(bucket string) (string, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return "", err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
// Generate presigned DELETE URL for bucket
presignedReq, err := presignClient.PresignDeleteBucket(
context.TODO(),
&s3.DeleteBucketInput{
Bucket: aws.String(bucket),
},
s3.WithPresignExpires(15*time.Minute),
)
if err != nil {
return "", err
}
fmt.Println("Delete Bucket URL:", presignedReq.URL)
fmt.Println("Use HTTP DELETE to remove this bucket")
return presignedReq.URL, nil
}Use cases:
Important notes:
func (c *PresignClient) PresignHeadBucket(
ctx context.Context,
params *HeadBucketInput,
optFns ...func(*PresignOptions),
) (*v4.PresignedHTTPRequest, error)Generates a presigned URL for checking if a bucket exists and if you have permission to access it.
Parameters:
ctx - Context for the requestparams - HeadBucket input parameters (bucket name, expected owner)optFns - Optional presign configuration functionsReturns:
*v4.PresignedHTTPRequest - Presigned request containing URL and headerserror - Any error encountered during presigningExample:
import (
"context"
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func generateCheckBucketURL(bucket string) (string, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return "", err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
// Generate presigned HEAD URL to check bucket existence
presignedReq, err := presignClient.PresignHeadBucket(
context.TODO(),
&s3.HeadBucketInput{
Bucket: aws.String(bucket),
},
s3.WithPresignExpires(10*time.Minute),
)
if err != nil {
return "", err
}
fmt.Println("Check Bucket URL:", presignedReq.URL)
fmt.Println("Use HTTP HEAD to verify bucket existence and access")
// Client would use: HEAD presignedReq.URL
// HTTP 200 = bucket exists and accessible
// HTTP 404 = bucket doesn't exist or no access
return presignedReq.URL, nil
}Advanced example with HTTP client:
import (
"context"
"fmt"
"net/http"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func checkBucketWithPresignedURL(bucket string) (bool, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return false, err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
// Generate presigned HEAD request
presignedReq, err := presignClient.PresignHeadBucket(
context.TODO(),
&s3.HeadBucketInput{
Bucket: aws.String(bucket),
},
s3.WithPresignExpires(5*time.Minute),
)
if err != nil {
return false, err
}
// Execute the presigned request
httpReq, err := http.NewRequest(presignedReq.Method, presignedReq.URL, nil)
if err != nil {
return false, err
}
// Add signed headers
for key, values := range presignedReq.SignedHeader {
for _, value := range values {
httpReq.Header.Add(key, value)
}
}
httpClient := &http.Client{}
resp, err := httpClient.Do(httpReq)
if err != nil {
return false, err
}
defer resp.Body.Close()
// Check response status
if resp.StatusCode == http.StatusOK {
fmt.Println("Bucket exists and is accessible")
return true, nil
} else if resp.StatusCode == http.StatusNotFound {
fmt.Println("Bucket does not exist or no access")
return false, nil
}
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}Use cases:
func (c *PresignClient) PresignUploadPart(
ctx context.Context,
params *UploadPartInput,
optFns ...func(*PresignOptions),
) (*v4.PresignedHTTPRequest, error)Generates a presigned URL for uploading a part in a multipart upload.
Parameters:
ctx - Context for the requestparams - UploadPart input parameters (bucket, key, upload ID, part number, etc.)optFns - Optional presign configuration functionsReturns:
*v4.PresignedHTTPRequest - Presigned request containing URL and headerserror - Any error encountered during presigningExample:
func generateMultipartUploadURLs(bucket, key, uploadID string, numParts int) ([]string, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return nil, err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
urls := make([]string, numParts)
for i := 0; i < numParts; i++ {
partNumber := int32(i + 1)
// Generate presigned URL for each part
presignedReq, err := presignClient.PresignUploadPart(
context.TODO(),
&s3.UploadPartInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
UploadId: aws.String(uploadID),
PartNumber: aws.Int32(partNumber),
},
s3.WithPresignExpires(time.Hour),
)
if err != nil {
return nil, err
}
urls[i] = presignedReq.URL
fmt.Printf("Part %d URL: %s\n", partNumber, presignedReq.URL)
}
return urls, nil
}Use cases:
func (c *PresignClient) PresignPostObject(
ctx context.Context,
params *PutObjectInput,
optFns ...func(*PresignPostOptions),
) (*PresignedPostRequest, error)Generates form fields for browser-based POST upload to S3. This is different from other presign methods as it returns form fields rather than a URL with query parameters.
Parameters:
ctx - Context for the requestparams - PutObject input parameters (bucket, key, metadata, etc.)optFns - Optional POST presign configuration functionsReturns:
*PresignedPostRequest - Contains URL and form fields for POST uploaderror - Any error encountered during presigningExample:
import (
"context"
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func generatePostUploadForm(bucket, key string) error {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
// Generate POST upload with conditions
postRequest, err := presignClient.PresignPostObject(
context.TODO(),
&s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
},
func(opts *s3.PresignPostOptions) {
opts.Expires = time.Hour
opts.Conditions = []interface{}{
// Restrict content type to images
[]interface{}{"starts-with", "$Content-Type", "image/"},
// Set maximum file size (10MB)
[]interface{}{"content-length-range", 1, 10485760},
// Set ACL
map[string]string{"acl": "public-read"},
}
},
)
if err != nil {
return err
}
// Generate HTML form
fmt.Println("<!-- HTML Form for Browser Upload -->")
fmt.Printf("<form action=\"%s\" method=\"post\" enctype=\"multipart/form-data\">\n", postRequest.URL)
// Add hidden form fields
for key, value := range postRequest.Values {
fmt.Printf(" <input type=\"hidden\" name=\"%s\" value=\"%s\" />\n", key, value)
}
// File input
fmt.Println(" <input type=\"file\" name=\"file\" />")
fmt.Println(" <input type=\"submit\" value=\"Upload\" />")
fmt.Println("</form>")
return nil
}Advanced example with metadata and conditions:
func generateAdvancedPostUpload(bucket, keyPrefix string) (*s3.PresignedPostRequest, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return nil, err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
postRequest, err := presignClient.PresignPostObject(
context.TODO(),
&s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(keyPrefix + "/${filename}"),
},
func(opts *s3.PresignPostOptions) {
opts.Expires = 2 * time.Hour
opts.Conditions = []interface{}{
// Key must start with specified prefix
[]interface{}{"starts-with", "$key", keyPrefix},
// Content-Type restrictions
[]interface{}{"starts-with", "$Content-Type", "image/"},
// File size limits (1KB to 5MB)
[]interface{}{"content-length-range", 1024, 5242880},
// Cache control
map[string]string{"Cache-Control": "max-age=3600"},
// Custom metadata
map[string]string{"x-amz-meta-uploader": "web-app"},
}
},
)
if err != nil {
return nil, err
}
return postRequest, nil
}Use cases:
type PresignPostOptions struct {
// ClientOptions are list of functional options to mutate client options used by
// the presign client.
ClientOptions []func(*Options)
// PostPresigner to use. One will be created if none is provided
PostPresigner PresignPost
// Expires sets the expiration duration for the generated presign url. This should
// be the duration in seconds the presigned URL should be considered valid for. If
// not set or set to zero, presign url would default to expire after 900 seconds.
Expires time.Duration
// Conditions a list of extra conditions to pass to the policy document
// Available conditions can be found at:
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html#sigv4-PolicyConditions
Conditions []interface{}
}Configuration options for POST presign operations.
Fields:
ClientOptions - []func(*Options)
S3 client configuration options to apply.
PostPresigner - PresignPost
POST presigner implementation. Automatically created if not provided.
Expires - time.Duration
Duration for which the POST upload remains valid. Default is 15 minutes (900 seconds).
Conditions - []interface{}
Policy conditions to restrict uploads. Each condition can be:
map[string]string{"key": "value"}[]interface{}{"starts-with", "$field", "prefix"}[]interface{}{"content-length-range", min, max}Common policy conditions:
key - Restrict object key/nameacl - Set object ACL (e.g., "public-read", "private")Content-Type - Restrict file MIME typecontent-length-range - Restrict file size (min, max bytes)Cache-Control - Set caching headersx-amz-meta-* - Custom metadata fieldsx-amz-server-side-encryption - Require encryptionsuccess_action_redirect - Redirect URL after successful uploadsuccess_action_status - HTTP status code to return after successExample conditions:
conditions := []interface{}{
// Exact match for ACL
map[string]string{"acl": "public-read"},
// Key must start with "uploads/"
[]interface{}{"starts-with", "$key", "uploads/"},
// Content-Type must start with "image/"
[]interface{}{"starts-with", "$Content-Type", "image/"},
// File size between 1KB and 10MB
[]interface{}{"content-length-range", 1024, 10485760},
// Custom metadata
map[string]string{"x-amz-meta-uuid": "user123"},
// Redirect after success
map[string]string{"success_action_redirect": "https://example.com/success"},
}type PresignedPostRequest struct {
// Represents the Base URL to make a request to
URL string
// Values is a key-value map of values to be sent as FormData
// these values are not encoded
Values map[string]string
}Result of POST presign operation containing URL and form fields.
Fields:
URL - string
The S3 endpoint URL where the form should be posted.
Values - map[string]string
Form fields to include in the POST request. These must be sent as form data along with the file.
Standard fields include:
key - Object key/namepolicy - Base64-encoded policy documentX-Amz-Algorithm - Signature algorithm (AWS4-HMAC-SHA256)X-Amz-Credential - AWS credentialsX-Amz-Date - Signing dateX-Amz-Signature - Request signatureX-Amz-Security-Token - Session token (if using temporary credentials)Usage in HTML form:
<form action="${URL}" method="post" enctype="multipart/form-data">
<!-- Hidden fields from Values map -->
<input type="hidden" name="key" value="${Values['key']}" />
<input type="hidden" name="X-Amz-Algorithm" value="${Values['X-Amz-Algorithm']}" />
<input type="hidden" name="X-Amz-Credential" value="${Values['X-Amz-Credential']}" />
<input type="hidden" name="X-Amz-Date" value="${Values['X-Amz-Date']}" />
<input type="hidden" name="policy" value="${Values['policy']}" />
<input type="hidden" name="X-Amz-Signature" value="${Values['X-Amz-Signature']}" />
<!-- File input must be last -->
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>Usage with JavaScript Fetch API:
const formData = new FormData();
// Add all form fields from Values
Object.entries(presignedPost.Values).forEach(([key, value]) => {
formData.append(key, value);
});
// Add file (must be last)
formData.append('file', fileInput.files[0]);
// POST to S3
const response = await fetch(presignedPost.URL, {
method: 'POST',
body: formData
});
if (response.ok) {
console.log('Upload successful');
} else {
console.error('Upload failed:', response.statusText);
}type HTTPPresignerV4 interface {
PresignHTTP(
ctx context.Context,
credentials aws.Credentials,
r *http.Request,
payloadHash string,
service string,
region string,
signingTime time.Time,
optFns ...func(*v4.SignerOptions),
) (url string, signedHeader http.Header, err error)
}Interface for SigV4 presigning HTTP requests. Implemented by v4.Signer.
Parameters:
ctx - Context for the operationcredentials - AWS credentials to sign withr - HTTP request to presignpayloadHash - SHA256 hash of request payloadservice - AWS service name (e.g., "s3")region - AWS regionsigningTime - Time to use for signingoptFns - Signer configuration optionsReturns:
url - Presigned URL stringsignedHeader - HTTP headers that are part of the signatureerr - Any error during presigning// From github.com/aws/aws-sdk-go-v2/aws/signer/v4
type PresignedHTTPRequest struct {
URL string
Method string
SignedHeader http.Header
}Result of GET/PUT/DELETE/HEAD presign operations.
Fields:
URL - string
The complete presigned URL including all query parameters. This URL can be used directly to make the HTTP request.
Method - string
HTTP method for the request (GET, PUT, DELETE, or HEAD).
SignedHeader - http.Header
HTTP headers that are part of the signature and must be included in the request. Typically includes Host and possibly custom headers.
Example usage:
// Generate presigned GET request
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String("my-bucket"),
Key: aws.String("my-object.txt"),
})
if err != nil {
panic(err)
}
// Use with http.Client
httpReq, err := http.NewRequest(presignedReq.Method, presignedReq.URL, nil)
if err != nil {
panic(err)
}
// Add signed headers
for key, values := range presignedReq.SignedHeader {
for _, value := range values {
httpReq.Header.Add(key, value)
}
}
// Execute request
httpClient := &http.Client{}
resp, err := httpClient.Do(httpReq)
if err != nil {
panic(err)
}
defer resp.Body.Close()type PresignPost interface {
PresignPost(
credentials aws.Credentials,
bucket string,
key string,
region string,
service string,
signingTime time.Time,
conditions []interface{},
expirationTime time.Time,
optFns ...func(*v4.SignerOptions),
) (fields map[string]string, err error)
}Interface for POST upload presigning. Implemented internally by the SDK.
Parameters:
credentials - AWS credentials for signingbucket - S3 bucket namekey - Object keyregion - AWS regionservice - Service name (typically "s3")signingTime - Current time for signingconditions - Policy conditionsexpirationTime - When the presigned POST expiresoptFns - Signer optionsReturns:
fields - Form fields to include in POST requesterr - Any error during presigningpackage main
import (
"context"
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func main() {
// Load AWS configuration
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
panic(err)
}
// Create S3 client
client := s3.NewFromConfig(cfg)
// Create presign client
presignClient := s3.NewPresignClient(client)
// Generate presigned GET URL
presignedReq, err := presignClient.PresignGetObject(
context.TODO(),
&s3.GetObjectInput{
Bucket: aws.String("my-bucket"),
Key: aws.String("documents/report.pdf"),
},
s3.WithPresignExpires(15*time.Minute),
)
if err != nil {
panic(err)
}
fmt.Println("Download link (valid for 15 minutes):")
fmt.Println(presignedReq.URL)
}package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func main() {
// Load AWS configuration
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
panic(err)
}
// Create S3 client and presign client
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
// HTTP handler for upload endpoint
http.HandleFunc("/get-upload-url", func(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("filename")
if filename == "" {
http.Error(w, "filename required", http.StatusBadRequest)
return
}
// Generate presigned PUT URL
presignedReq, err := presignClient.PresignPutObject(
context.TODO(),
&s3.PutObjectInput{
Bucket: aws.String("my-uploads"),
Key: aws.String("uploads/" + filename),
},
s3.WithPresignExpires(time.Hour),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Return URL to client
json.NewEncoder(w).Encode(map[string]string{
"url": presignedReq.URL,
"method": presignedReq.Method,
})
})
fmt.Println("Server starting on :8080")
http.ListenAndServe(":8080", nil)
}Client-side JavaScript:
async function uploadFile(file) {
// Get presigned URL from server
const response = await fetch(`/get-upload-url?filename=${file.name}`);
const { url, method } = await response.json();
// Upload directly to S3
const uploadResponse = await fetch(url, {
method: method,
body: file,
headers: {
'Content-Type': file.type
}
});
if (uploadResponse.ok) {
console.log('Upload successful!');
} else {
console.error('Upload failed:', uploadResponse.statusText);
}
}package main
import (
"context"
"fmt"
"html/template"
"net/http"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
const formTemplate = `
<!DOCTYPE html>
<html>
<head>
<title>Upload to S3</title>
</head>
<body>
<h1>Upload Image to S3</h1>
<form action="{{.URL}}" method="post" enctype="multipart/form-data">
{{range $key, $value := .Values}}
<input type="hidden" name="{{$key}}" value="{{$value}}" />
{{end}}
<input type="file" name="file" accept="image/*" required />
<button type="submit">Upload</button>
</form>
</body>
</html>
`
func main() {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
panic(err)
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
http.HandleFunc("/upload-form", func(w http.ResponseWriter, r *http.Request) {
// Generate presigned POST request
postRequest, err := presignClient.PresignPostObject(
context.TODO(),
&s3.PutObjectInput{
Bucket: aws.String("my-uploads"),
Key: aws.String("images/${filename}"),
},
func(opts *s3.PresignPostOptions) {
opts.Expires = time.Hour
opts.Conditions = []interface{}{
// Only allow images
[]interface{}{"starts-with", "$Content-Type", "image/"},
// Max 5MB file size
[]interface{}{"content-length-range", 1, 5242880},
// Set public-read ACL
map[string]string{"acl": "public-read"},
}
},
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Render HTML form with presigned fields
tmpl := template.Must(template.New("form").Parse(formTemplate))
tmpl.Execute(w, postRequest)
})
fmt.Println("Upload form server starting on :8080")
fmt.Println("Visit http://localhost:8080/upload-form")
http.ListenAndServe(":8080", nil)
}package main
import (
"context"
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
type MultipartUploadSession struct {
UploadID string
PartURLs []string
}
func createMultipartUploadSession(bucket, key string, numParts int) (*MultipartUploadSession, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return nil, err
}
client := s3.NewFromConfig(cfg)
// Initiate multipart upload
createResp, err := client.CreateMultipartUpload(
context.TODO(),
&s3.CreateMultipartUploadInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
},
)
if err != nil {
return nil, err
}
uploadID := *createResp.UploadId
// Create presign client
presignClient := s3.NewPresignClient(client)
// Generate presigned URLs for each part
partURLs := make([]string, numParts)
for i := 0; i < numParts; i++ {
partNumber := int32(i + 1)
presignedReq, err := presignClient.PresignUploadPart(
context.TODO(),
&s3.UploadPartInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
UploadId: aws.String(uploadID),
PartNumber: aws.Int32(partNumber),
},
s3.WithPresignExpires(time.Hour),
)
if err != nil {
return nil, err
}
partURLs[i] = presignedReq.URL
}
return &MultipartUploadSession{
UploadID: uploadID,
PartURLs: partURLs,
}, nil
}
func main() {
// Create session with 5 parts
session, err := createMultipartUploadSession(
"my-bucket",
"large-file.zip",
5,
)
if err != nil {
panic(err)
}
fmt.Println("Upload ID:", session.UploadID)
fmt.Println("\nUpload each part to:")
for i, url := range session.PartURLs {
fmt.Printf("Part %d: %s\n", i+1, url)
}
fmt.Println("\nAfter uploading all parts, complete the multipart upload with CompleteMultipartUpload")
}package main
import (
"context"
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
)
func generateDownloadWithMetadata(bucket, key string) (string, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return "", err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
// Generate presigned GET URL with custom response headers
presignedReq, err := presignClient.PresignGetObject(
context.TODO(),
&s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
// Override response headers
ResponseContentType: aws.String("application/pdf"),
ResponseContentDisposition: aws.String("attachment; filename=\"report.pdf\""),
ResponseCacheControl: aws.String("no-cache"),
},
s3.WithPresignExpires(30*time.Minute),
)
if err != nil {
return "", err
}
return presignedReq.URL, nil
}
func generateUploadWithEncryption(bucket, key string) (string, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return "", err
}
client := s3.NewFromConfig(cfg)
presignClient := s3.NewPresignClient(client)
// Generate presigned PUT URL with server-side encryption
presignedReq, err := presignClient.PresignPutObject(
context.TODO(),
&s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
ServerSideEncryption: types.ServerSideEncryptionAes256,
Metadata: map[string]string{
"uploader": "admin",
"purpose": "backup",
},
},
s3.WithPresignExpires(time.Hour),
)
if err != nil {
return "", err
}
return presignedReq.URL, nil
}
func main() {
// Download with custom headers
downloadURL, err := generateDownloadWithMetadata("my-bucket", "data.csv")
if err != nil {
panic(err)
}
fmt.Println("Download URL:", downloadURL)
// Upload with encryption and metadata
uploadURL, err := generateUploadWithEncryption("my-bucket", "backup.tar.gz")
if err != nil {
panic(err)
}
fmt.Println("Upload URL:", uploadURL)
}Use appropriate expiration times:
Apply least privilege:
Use HTTPS:
Consider additional validation:
success_action_redirect to handle upload resultsCache presigned URLs:
Use appropriate method:
Parallel multipart uploads:
func safePresignGetObject(ctx context.Context, client *s3.PresignClient, bucket, key string) (string, error) {
if bucket == "" || key == "" {
return "", fmt.Errorf("bucket and key are required")
}
presignedReq, err := client.PresignGetObject(
ctx,
&s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
},
s3.WithPresignExpires(15*time.Minute),
)
if err != nil {
// Log error with context
return "", fmt.Errorf("failed to presign GET for %s/%s: %w", bucket, key, err)
}
if presignedReq.URL == "" {
return "", fmt.Errorf("presigned URL is empty")
}
return presignedReq.URL, nil
}Forgetting signed headers:
PresignedHTTPRequest.SignedHeaderURL encoding issues:
Clock skew:
Expired credentials:
Wrong HTTP method:
PresignedHTTPRequest.Method