ex_aws_auth

View Source

Hex.pm Hexdocs.pm CI

AWS Signature Version 4 signing library for Elixir. Small, focused, and easy to use.

Features

  • Req Plugin: Automatic request signing for the Req HTTP client
  • Clean API: Modern credential struct-based interface
  • Session Tokens: Full support for temporary AWS credentials (STS)
  • Flexible Output: Choose between list, map, or Req-compatible header formats
  • Smart Defaults: Auto-detect service and region from AWS URLs
  • Presigned URLs: Configurable expiration times (15 min default, up to 7 days)
  • Streaming Support: Unsigned payload option for large file uploads
  • Backward Compatible: Maintains compatibility with the original aws_auth API

Installation

Add ex_aws_auth to your dependencies in mix.exs:

def deps do
  [
    {:ex_aws_auth, "~> 1.3"},
    {:req, "~> 0.5"}  # Optional, for Req plugin
  ]
end

Quick Start

The easiest way to use this library is with the Req plugin:

# Load credentials from environment (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, etc.)
creds = AWSAuth.Credentials.from_env()

# Make authenticated requests
Req.new(url: "https://s3.amazonaws.com/my-bucket/file.txt")
|> AWSAuth.Req.attach(credentials: creds, service: "s3")
|> Req.get!()

# POST with body
Req.new(url: "https://bedrock-runtime.us-east-1.amazonaws.com/model/my-model/invoke")
|> AWSAuth.Req.attach(credentials: creds, service: "bedrock")
|> Req.post!(json: %{prompt: "Hello"})

Manual Signing

For manual control over request signing:

# Create credentials
creds = %AWSAuth.Credentials{
  access_key_id: "AKIAIOSFODNN7EXAMPLE",
  secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  region: "us-east-1"
}

# Sign a URL (for presigned URLs)
signed_url = AWSAuth.sign_url(
  creds,
  "GET",
  "https://s3.amazonaws.com/my-bucket/file.txt",
  "s3"
)

# Sign request headers (for API calls)
headers = AWSAuth.sign_authorization_header(
  creds,
  "POST",
  "https://bedrock-runtime.us-east-1.amazonaws.com/model/my-model/invoke",
  "bedrock",
  headers: %{"content-type" => "application/json"},
  payload: Jason.encode!(%{prompt: "Hello"})
)

Credentials

From Environment Variables

# Reads AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_REGION
creds = AWSAuth.Credentials.from_env()

Manual Creation

creds = %AWSAuth.Credentials{
  access_key_id: "AKIAIOSFODNN7EXAMPLE",
  secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  session_token: "FwoGZXIvYXdzEBYa...",  # Optional, for STS temporary credentials
  region: "us-east-1"
}

From Map or Keyword List

creds = AWSAuth.Credentials.from_map(%{
  access_key_id: "...",
  secret_access_key: "...",
  region: "us-west-2"
})

Advanced Usage

Auto-Detection of Service and Region

Skip specifying service and region when using standard AWS URLs:

creds = AWSAuth.Credentials.from_env()

# Service and region detected from URL
signed_url = AWSAuth.sign_url(
  creds,
  "GET",
  "https://s3.us-west-2.amazonaws.com/my-bucket/file.txt",
  nil  # nil = auto-detect
)

# Works with any AWS service
headers = AWSAuth.sign_authorization_header(
  creds,
  "POST",
  "https://bedrock-runtime.us-east-1.amazonaws.com/model/invoke",
  nil,
  payload: body
)

Presigned URLs with Custom Expiration

Control how long presigned URLs remain valid:

# Default: 15 minutes
signed_url = AWSAuth.sign_url(creds, "GET", url, "s3")

# Custom expiration: 1 hour
signed_url = AWSAuth.sign_url(creds, "GET", url, "s3", expires_in: 3600)

# Maximum: 7 days
signed_url = AWSAuth.sign_url(creds, "GET", url, "s3", expires_in: 604_800)

Streaming Uploads (Unsigned Payload)

For large file uploads where the payload isn't known upfront:

# Use :unsigned for streaming
headers = AWSAuth.sign_authorization_header(
  creds,
  "PUT",
  "https://s3.amazonaws.com/bucket/large-file.zip",
  "s3",
  payload: :unsigned
)

Session Tokens (STS Temporary Credentials)

Full support for AWS Security Token Service temporary credentials:

creds = %AWSAuth.Credentials{
  access_key_id: "ASIAIOSFODNN7EXAMPLE",  # Note: Starts with ASIA for temporary creds
  secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  session_token: "FwoGZXIvYXdzEBYaDHhBTEMPLESessionToken123",
  region: "us-east-1"
}

# Automatically includes X-Amz-Security-Token in signatures
Req.new(url: url)
|> AWSAuth.Req.attach(credentials: creds, service: "s3")
|> Req.get!()

Header Format Options

Choose your preferred header format:

# List of tuples (default, works with most HTTP clients)
headers = AWSAuth.sign_authorization_header(creds, method, url, service,
  return_format: :list
)
# => [{"authorization", "AWS4-HMAC-SHA256 ..."}, {"x-amz-date", "..."}]

# Map (convenient for merging)
headers = AWSAuth.sign_authorization_header(creds, method, url, service,
  return_format: :map
)
# => %{"authorization" => "AWS4-HMAC-SHA256 ...", "x-amz-date" => "..."}

# Req format (for use with Req)
headers = AWSAuth.sign_authorization_header(creds, method, url, service,
  return_format: :req
)
# => %{"authorization" => ["AWS4-HMAC-SHA256 ..."], "x-amz-date" => ["..."]}

Advanced Options

Fine-tune signing behavior for edge cases:

# Exclude specific headers from signing
headers = AWSAuth.sign_authorization_header(creds, "POST", url, "service",
  unsigned_headers: ["user-agent", "accept-encoding"]
)

# Disable URI path escaping (for pre-encoded paths)
signed_url = AWSAuth.sign_url(creds, "GET", url, "s3",
  uri_escape_path: false
)

# Omit content checksum header (rare)
headers = AWSAuth.sign_authorization_header(creds, "POST", url, "service",
  apply_checksum_header: false
)

# Override region from credentials
signed_url = AWSAuth.sign_url(creds, "GET", url, "s3",
  region: "eu-west-1"
)

Legacy API (Backward Compatibility)

The original aws_auth API is still supported for backward compatibility:

# Old-style URL signing (still works)
signed_url = AWSAuth.sign_url(
  "AKIAIOSFODNN7EXAMPLE",
  "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "GET",
  "https://examplebucket.s3.amazonaws.com/test.txt",
  "us-east-1",
  "s3"
)

# Old-style header signing (still works)
auth_header = AWSAuth.sign_authorization_header(
  "AKIAIOSFODNN7EXAMPLE",
  "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "PUT",
  "https://examplebucket.s3.amazonaws.com/test.txt",
  "us-east-1",
  "s3",
  %{"x-amz-storage-class" => "REDUCED_REDUNDANCY"},
  "file contents"
)

Note: While the legacy API is fully supported, we recommend using the new Credentials-based API or Req plugin for new projects.

Why ex_aws_auth?

The original aws_auth package was created by Bryan Joseph. Unfortunately, it is no longer actively maintained and has become incompatible with modern versions of Erlang/OTP.

Rodrigo Zampieri Castilho forked it and brought it up to date for OTP 27, but his fork cannot be included as a dependency in packages published on Hex.

This package, ex_aws_auth, is published on Hex to make this essential AWS signing functionality available to the broader Elixir community, with modern improvements and active maintenance.

Full credit and thanks go to Bryan Joseph for creating the original library and Rodrigo Zampieri Castilho for maintaining compatibility with modern OTP versions.

Documentation

Full documentation is available on HexDocs.

License

Apache License 2.0 - See LICENSE file for details.