AnnouncementsIntroducing Arkoren

    Menu

    Signed URLs

    Authorize direct S3 transfers, bind upload headers and exact size, and distinguish provider URLs from application-signed routes.

    What's on this page
    1. Grant short-lived direct downloads after authorizing the stored object.
    2. Bind direct uploads to an application-owned key, content type, and exact byte length.
    3. Confirm uploaded content and handle unsupported signing without weakening restrictions.
    4. Distinguish provider capabilities from signed application routes and their transfer costs.
    On this page

    A large transfer need not pass through your application server. A signed URL grants a client GET or PUT access to one object directly at the provider. Your application authorizes the recipient and signs the request; the provider serves the bytes without that client presenting your storage credentials.

    The URL is itself a bearer credential. Anyone holding it can use the granted operation while the provider accepts it, so return it only to an authorized recipient and keep it out of logs, analytics, and shared diagnostics. Facade telemetry excludes signed URLs.

    Examples assume files *storage.Storage backed by the S3 driver and a non-nil operation context ctx. Import storage from arkoren.dev/core/service/storage, plus standard time, net/http, and errors where used. Memory and filesystem drivers do not sign provider URLs.

    Signed Downloads

    After loading and authorizing a private invoice record, use its stored key to issue a short-lived download. Here invoice.Key is an application-owned string:

    address, err := files.Sign(ctx, invoice.Key, storage.SignOptions{
    	TTL: 15 * time.Minute,
    })

    An omitted method means GET. Return the string only after checking err; SignURL provides a parsed *url.URL instead. Do not edit the signed path or query. The configured S3 prefix applies, so this addresses the same logical object as Open or Put.

    Signing does not open the object or check existence. A signed GET for a missing key fails when fetched; use Stat first if your issuance flow needs that check. Provider requests do not pass through your app’s authorization middleware or Download/Inline presentation safeguards. Review bucket permissions, stored content metadata, and the origin that serves untrusted content separately.

    TTL must be positive. S3 accepts at most seven days; larger values wrap storage.ErrInvalidTTL. Temporary AWS credentials can expire sooner, making the URL unusable before its requested TTL. Prefer short grants and issue new ones when needed. This API does not supply one-time consumption or per-link revocation; do not infer either from the TTL.

    Direct Uploads

    A signed PUT permits replacing one key and can be reused while valid. Before issuing it, authorize the upload target and allocate an application-owned key that does not expose another user’s object. Do not accept an arbitrary key or filename from the client.

    For an avatar, accept a decoded request with a claimed exact size. The format policy is PNG, selected by your application rather than the client’s MIME claim:

    type AvatarUpload struct {
    	Size int64 `json:"size"`
    }

    With input AvatarUpload, check its positive size against the application limit before signing. Import problem from arkoren.dev/foundation/problem for this handler fragment:

    if input.Size <= 0 || input.Size > 2<<20 {
    	return problem.UnprocessableContent().Derive(func(details *problem.Builder) {
    		details.Detail("The avatar must contain between 1 byte and 2 MiB.")
    	})
    }

    Here key is the upload key you allocated for the authorized user. Bind both the expected type and exact byte length:

    destination, err := files.Sign(ctx, key, storage.SignOptions{
    	Method:        http.MethodPut,
    	TTL:           10 * time.Minute,
    	ContentType:   "image/png",
    	ContentLength: input.Size,
    })
    if err != nil {
    	return err
    }

    Only expose destination after success. The uploader must send PUT, Content-Type: image/png, and exactly input.Size bytes with the matching Content-Length. Signing does not send these headers for the client. Binding the MIME header does not establish that the bytes are a valid or safe image.

    For browser uploads, configure bucket CORS to allow your application origin, PUT, and the Content-Type request header; follow the S3 CORS instructions. CORS is not authorization. Allow your real origin rather than every origin. Given the selected browser File named file, whose size matches the signed request, upload with:

    const uploaded = await fetch(destination, {
      method: "PUT",
      headers: { "Content-Type": "image/png" },
      body: file,
    });
    if (!uploaded.ok) {
      throw new Error(`Upload failed: ${uploaded.status}`);
    }

    The browser manages Content-Length; do not try to set that forbidden request header manually.

    Upload Bindings

    An empty ContentType leaves the type unbound. Zero ContentLength leaves size unbound, not restricted to an empty upload. A positive length is an exact match, not a maximum. S3 presigned PUT cannot express a size range; presigned POST policies are a different mechanism that Arkoren does not offer.

    Content type can be bound without length, but use both for user uploads. Otherwise the URL holder selects any unbound type or size. A negative length, or type/length bindings on GET, wraps storage.ErrInvalidSignOptions.

    With a client supplied through Native Access, the presigner must report the requested type as the sole signed Content-Type value in its v4.PresignedHTTPRequest.SignedHeader. A missing or changed type produces storage.ErrUnsupported instead of a weaker URL. Arkoren does not modify borrowed middleware or close the supplied client.

    For an S3-compatible service, verify the provider actually enforces signatures and bound headers. Signing success alone does not test server enforcement. Never recover from unsupported bindings by silently retrying without them.

    Confirming Uploads

    A direct client write bypasses facade upload validation, write events, and transfer telemetry. Keep the allocated key unconfirmed until your application has verified the upload. When the client reports completion, authorize that confirmation against the same owner’s pending upload and inspect it:

    info, err := files.Stat(ctx, key)

    After checking err, compare the size with the pending record and validate contents before publishing the object’s application record. Stat establishes metadata, not content safety. A holder can reuse the PUT URL while valid, so a completion callback alone does not make the object immutable. If your workflow requires a validated immutable asset, design a separate trusted promotion step rather than serving a still-writable pending key.

    Unsupported Signing

    Memory and filesystem stores return an error wrapping storage.ErrUnsupported; an S3 client without presigning, or unable to retain the requested type binding, does too. Call the operation and classify its result:

    destination, err := files.Sign(ctx, key, options)
    if errors.Is(err, storage.ErrUnsupported) {
    	// Offer an application-served transfer with the same authorization policy.
    }

    Here options is your validated storage.SignOptions. Do not return destination on error. A fallback can accept a bounded upload, not a less restricted signed request. Test the fallback with memory storage; exercise provider signing separately against the deployment backend.

    Cancellation precedes input validation. Otherwise validation checks key, TTL and backend cap, method, bindings, then signing capability. Only GET and PUT are supported; unsupported methods also wrap ErrUnsupported. An invalid request does not become valid merely because the driver cannot sign.

    If the backend has no direct URL, or your application must check policy on each request, sign an application route with Secure Links and serve its authorized record using Download. This is a separate mechanism: your route verifies the link and your server transfers the bytes. Storage signing does not install route authentication, create a receiver, or add download-count enforcement.

    For signing options and error identities, see the storage API reference.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive