ExAtlas.Auth.SignedUrl (ExAtlas v0.5.0)

Copy Markdown View Source

HMAC-signed URLs with expiry, for the cases where a client can't set request headers (e.g. <video src>, <img src>, WebSocket upgrade without a subprotocol).

The approach mirrors S3 presigned URLs: ExAtlas appends two query parameters — exp (a Unix timestamp when the URL stops working) and sig (the HMAC-SHA256 of the URL's path + exp). The pod-side inference server recomputes the HMAC and compares with Plug.Crypto.secure_compare/2.

The signing secret is not the bearer token from ExAtlas.Auth.Token. It's a longer-lived per-pod (or per-user) secret you generate yourself, pass to the pod via an env var, and use to sign URLs on the Phoenix side. ExAtlas gives you both halves — signing and verification — to keep everything symmetric.

Example

secret = :crypto.strong_rand_bytes(32) |> Base.url_encode64()

signed =
  ExAtlas.Auth.SignedUrl.sign(
    "https://abc-8000.proxy.runpod.net/stream/session-42",
    secret: secret,
    expires_in: 3600
  )

# => "https://abc-8000.proxy.runpod.net/stream/session-42?exp=1747000000&sig=..."

# In the pod's Plug pipeline:
:ok = ExAtlas.Auth.SignedUrl.verify(conn.request_path <> "?" <> conn.query_string,
                                 secret: secret)

Summary

Functions

Sign a URL. Returns the URL with exp and sig query parameters appended.

Verify a signed URL.

Types

secret()

@type secret() :: String.t() | iodata()

url()

@type url() :: String.t()

Functions

sign(url, opts)

@spec sign(
  url(),
  keyword()
) :: url()

Sign a URL. Returns the URL with exp and sig query parameters appended.

Options

  • :secret (required) — HMAC signing secret.
  • :expires_in — seconds from now until the signature expires (default 3600).
  • :now — override the current time (integer Unix seconds; for testing).

verify(url, opts)

@spec verify(
  url(),
  keyword()
) :: :ok | {:error, :expired | :bad_signature | :malformed}

Verify a signed URL.

Accepts either the full URL or just the path + query string.

Returns :ok if the signature is valid and unexpired, or an error tuple: {:error, :expired | :bad_signature | :malformed}.