Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions docs/technical-guide/auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# Auth System

> **Last updated:** April 29, 2026

The auth system provides pluggable authentication and signing for Smithy-Java clients. It's layered across four modules:
shared identity abstractions, client-side auth scheme resolution, AWS-specific identity types, and the SigV4 signing
implementation.

**Source:**
- [`auth-api/`](https://github.com/smithy-lang/smithy-java/tree/main/auth-api) — Shared abstractions
- [`client/client-auth-api/`](https://github.com/smithy-lang/smithy-java/tree/main/client/client-auth-api) — Client auth scheme resolution
- [`aws/aws-auth-api/`](https://github.com/smithy-lang/smithy-java/tree/main/aws/aws-auth-api) — AWS identity types
- [`aws/aws-sigv4/`](https://github.com/smithy-lang/smithy-java/tree/main/aws/aws-sigv4) — SigV4 implementation

## Core Abstractions (`auth-api`)

### Identity

```java
public interface Identity {
default Instant expirationTime() { return null; }
}
```

Built-in identity types:
- `TokenIdentity` — bearer token (`String token()`)
- `ApiKeyIdentity` — API key (`String apiKey()`)
- `LoginIdentity` — username/password

### IdentityResolver

```java
public interface IdentityResolver<IdentityT extends Identity> {
IdentityResult<IdentityT> resolveIdentity(Context requestProperties);
Class<IdentityT> identityType();
static <I extends Identity> IdentityResolver<I> chain(List<IdentityResolver<I>> resolvers);
static <I extends Identity> IdentityResolver<I> of(I identity); // static resolver
}
```

Returns `IdentityResult<T>` (success-or-error wrapper) instead of throwing. Expected failures (missing env vars) return
`IdentityResult.ofError()`. `IdentityResolverChain` tries resolvers in order, returns first success.

### IdentityResolvers (Registry)

```java
public interface IdentityResolvers {
<T extends Identity> IdentityResolver<T> identityResolver(Class<T> identityClass);
static IdentityResolvers of(IdentityResolver<?>... resolvers);
}
```

Type-safe registry mapping identity class → resolver. Used by `AuthScheme.identityResolver(resolvers)`.

### Signer

```java
@FunctionalInterface
public interface Signer<RequestT, IdentityT extends Identity> extends AutoCloseable {
SignResult<RequestT> sign(RequestT request, IdentityT identity, Context properties);
}
```

`SignResult<RequestT>` is a record: `(RequestT signedRequest, String signature)`. The signature string is used as the
seed for event stream signing.

## Client Auth API (`client-auth-api`)

### AuthScheme

```java
public interface AuthScheme<RequestT, IdentityT extends Identity> {
ShapeId schemeId();
Class<RequestT> requestClass();
Class<IdentityT> identityClass();
default IdentityResolver<IdentityT> identityResolver(IdentityResolvers resolvers);
default Context getSignerProperties(Context context);
default Context getIdentityProperties(Context context);
Signer<RequestT, IdentityT> signer();
default <F extends Frame<?>> FrameProcessor<F> eventSigner(...);
}
```

An AuthScheme bundles: scheme ID (e.g., `aws.auth#sigv4`), identity resolver lookup, and signer. `getSignerProperties()`
/ `getIdentityProperties()` extract scheme-specific config from the client context.

### AuthSchemeResolver

```java
@FunctionalInterface
public interface AuthSchemeResolver {
List<AuthSchemeOption> resolveAuthScheme(AuthSchemeResolverParams params);
}
```

`DefaultAuthSchemeResolver` iterates `operation.effectiveAuthSchemes()` and wraps each in an `AuthSchemeOption`. The
client pipeline picks the first option with a matching scheme, compatible request class, and available identity
resolver.

### AuthSchemeFactory (SPI)

```java
public interface AuthSchemeFactory<T extends Trait> {
ShapeId schemeId();
AuthScheme<?, ?> createAuthScheme(T trait);
}
```

Discovered via `ServiceLoader`. Receives the Smithy trait instance and creates a configured `AuthScheme`.

## Pipeline Integration

Auth resolution happens in `ClientPipeline.doSendOrRetry()` between the `modifyBeforeSigning` and `readBeforeSigning`
interceptor hooks:

1. Build `AuthSchemeResolverParams` with protocol ID, operation, and context
2. Call `authSchemeResolver.resolveAuthScheme(params)` → priority-ordered `List<AuthSchemeOption>`
3. Iterate options, look up each `schemeId` in `supportedAuthSchemes`
4. Check `authScheme.requestClass().isAssignableFrom(request.getClass())`
5. Merge identity/signer properties from scheme defaults + option overrides
6. Call `authScheme.identityResolver(identityResolvers)` — skip if null
7. Call `resolver.resolveIdentity(identityProperties)`
8. First scheme with a non-null resolver becomes the `ResolvedScheme`
9. After endpoint resolution, apply endpoint auth scheme property overrides
10. `authScheme.signer().sign(request, identity, signerProperties)` → signed request

### Property Layering

Signer properties are merged from three sources (later overrides earlier):
1. Scheme defaults (`authScheme.getSignerProperties(context)`)
2. Resolver overrides (`AuthSchemeOption.signerPropertyOverrides()`)
3. Endpoint overrides (`applyEndpointAuthSchemeOverrides`)

## AWS Auth (`aws-auth-api`)

### AwsCredentialsIdentity

```java
public interface AwsCredentialsIdentity extends Identity {
String accessKeyId();
String secretAccessKey();
default String sessionToken() { return null; }
default String accountId() { return null; }
}
```

### AwsCredentialsResolver

```java
public interface AwsCredentialsResolver extends IdentityResolver<AwsCredentialsIdentity> {
@Override default Class<AwsCredentialsIdentity> identityType() {
return AwsCredentialsIdentity.class;
}
}
```

## SigV4 Implementation (`aws-sigv4`)

### SigV4AuthScheme

```java
public final class SigV4AuthScheme implements AuthScheme<HttpRequest, AwsCredentialsIdentity> {
public SigV4AuthScheme(String signingName);
// schemeId() → "aws.auth#sigv4"
// getSignerProperties() extracts SIGNING_NAME, REGION, CLOCK from context
// signer() → SigV4Signer.create()
// eventSigner() → SigV4EventSigner
}
```

Its inner `Factory` class implements `AuthSchemeFactory<SigV4Trait>` and is registered via SPI.

### SigV4Signer — Signing Flow

1. Extract `region`, `signingName`, `clock` from properties
2. Compute payload hash (SHA-256 hex of body)
3. Build canonical request: method + path + query + sorted headers + signed headers + payload hash
4. Derive signing key: `HMAC(HMAC(HMAC(HMAC("AWS4"+secret, date), region), service), "aws4_request")`
- Cached in `SigningCache` (bounded LRU, 300 entries, `StampedLock`-protected)
- Valid for the same calendar day
5. Compute signature: `HMAC(signingKey, stringToSign)`
6. Build `Authorization` header

Performance optimizations:
- `SigningResources` pools `StringBuilder`, `MessageDigest`, and `Mac` instances
- `Pool<T>` uses `ConcurrentLinkedQueue` (32 max)
- `SigningCache` uses `LinkedHashMap` with FIFO eviction and `StampedLock`
- Manual date formatting (avoids `DateTimeFormatter`)

### SigV4EventSigner

Implements chained event stream signing (`AWS4-HMAC-SHA256-PAYLOAD`). Each frame's signature depends on the previous
frame's signature. Produces frames with `:date` and `:chunk-signature` headers. Returns a `closingFrame()` that signs an
empty payload.

## Auth Scheme Discovery

**Generated clients**: Auth schemes are hardcoded by codegen based on `AuthSchemeFactory` SPI.

**Dynamic client**: `SimpleAuthDetectionPlugin` discovers auth schemes at runtime via `ServiceLoader`, reads effective
auth schemes from the model via `ServiceIndex.getEffectiveAuthSchemes()`, and creates schemes via factories.

## Configuration Points

Users can customize auth at three levels:
1. **Client builder** — `putSupportedAuthSchemes()`, `authSchemeResolver()`, `addIdentityResolver()`
2. **Plugins** — `ClientPlugin.configureClient()` can add schemes, resolvers, identity resolvers
3. **Per-request** — `RequestOverrideConfig` can override auth scheme resolver, add schemes, add identity resolvers
143 changes: 143 additions & 0 deletions docs/technical-guide/aws-protocols.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# AWS Protocol Integrations

> **Last updated:** April 29, 2026

Smithy-Java implements four AWS-specific protocols as `ClientProtocol` plugins, plus a shared event streaming
infrastructure. These protocols differ in serialization format, operation routing, and HTTP binding usage, but all share
the same client pipeline and auth system.

**Source:** [`aws/client/`](https://github.com/smithy-lang/smithy-java/tree/main/aws/client)

## Protocol Hierarchy

```
ClientProtocol<HttpRequest, HttpResponse>
└── HttpClientProtocol (abstract)
├── HttpBindingClientProtocol<F> (abstract, REST-style)
│ ├── RestJsonClientProtocol — aws.protocols#restJson1
│ └── RestXmlClientProtocol — aws.protocols#restXml
├── AwsJsonProtocol (abstract sealed, RPC-style)
│ ├── AwsJson1Protocol — aws.protocols#awsJson1_0
│ └── AwsJson11Protocol — aws.protocols#awsJson1_1
└── AwsQueryClientProtocol — aws.protocols#awsQuery
```

All protocols are registered via `ClientProtocolFactory` SPI in `META-INF/services`.

## AWS JSON 1.0 / 1.1

**Module:** `aws-client-awsjson`

RPC-style protocol, all data goes in the body, no HTTP binding traits.

- **Request**: Always `POST`. Sets `X-Amz-Target: {ServiceName}.{OperationName}`. Body is JSON-serialized
input. Content-Type: `application/x-amz-json-1.0` or `1.1`.
- **Response**: JSON body. Empty body → deserialize from `{}`.
- **Error detection**: `x-amzn-errortype` header first, then `__type` and `code` fields in JSON body. JSON 1.0 strips
URI prefix from `__type`.
- **Codec**: `JsonCodec` with `useTimestampFormat(true)` but NOT `useJsonName(true)`.

## AWS restJson1

**Module:** `aws-client-restjson`

REST-style protocol, uses HTTP binding traits (`@http`, `@httpHeader`, `@httpQuery`, `@httpLabel`, `@httpPayload`,
`@httpPrefixHeaders`).

- **Request**: HTTP method and URI pattern from `@http` trait. Headers, query params, path labels from binding
traits. Body is JSON for non-bound members.
- **Response**: HTTP binding deserialization for headers, status code, payload.
- **Error detection**: `x-amzn-errortype` header first, then `__type` in JSON body. Uses `HttpBindingErrorFactory` for
known errors.
- **Codec**: `JsonCodec` with `useJsonName(true)` AND `useTimestampFormat(true)`.
- **Key difference from AWS JSON**: Uses `@jsonName` trait, omits empty payloads, supports struct payloads via
`@httpPayload`.

## AWS restXml

**Module:** `aws-client-restxml`

REST-style protocol, same HTTP binding support as restJson1 but with XML body.

- **Request/Response**: Same HTTP binding pattern as restJson1 but body is XML.
- **Error detection**: `x-amzn-errortype` header first, then XML error code via `XmlUtil.parseErrorCodeName()`.
- **Codec**: `XmlCodec`.

## AWS Query

**Module:** `aws-client-awsquery`

The most unique protocol, asymmetric serialization formats.

- **Request**: Always `POST` with `Content-Type: application/x-www-form-urlencoded`. Body format:
`Action={OperationName}&Version={version}&Param1=Value1&Param2.SubParam=Value2`. Uses custom `AwsQueryFormSerializer`
with dot-delimited nested parameters. Respects `@xmlName` and `@xmlFlattened` traits.
- **Response**: XML body with wrapper elements (`{OperationName}Response` → `{OperationName}Result`). Uses `XmlCodec`
with wrapper element configuration.
- **Error detection**: XML error code via `XmlUtil.parseErrorCodeName()`. Checks `@awsQueryError` trait custom codes on
operation error schemas.
- **Requires**: Both `service` and `serviceVersion` settings (unlike other protocols).
- **Does NOT support**: Event streaming, document types.

## Event Streaming

**Module:** `aws-event-streams`

All AWS protocols (except Query) share the same event streaming infrastructure based on the AWS Event Stream binary
message format (`application/vnd.amazon.eventstream`).

### Core Types

- `AwsEventFrame` — wraps `software.amazon.eventstream.Message`, implements `Frame<Message>`
- `AwsEventEncoderFactory` — creates encoders for input/output streams
- `AwsEventDecoderFactory` — creates decoders for input/output streams

### Encoding Flow

1. Determine if event is initial request/response or a union event member
2. Handle `@eventHeader` members → event message headers
3. Handle `@eventPayload` members → blob (raw bytes), string (UTF-8), or codec-serialized
4. Regular members → codec-serialized as payload
5. Error events: modeled exceptions get `:exception-type` header; unmodeled get `:error-code` + `:error-message`

### Decoding Flow

1. Read `:message-type` header: `"event"`, `"error"`, or `"exception"`
2. For errors: extract `:error-code` and `:error-message`, throw `EventStreamingException`
3. For exceptions: read `:exception-type`, deserialize as modeled error
4. For events: read `:event-type`, find matching union member, deserialize payload + headers

### RPC vs REST Event Streaming

- **RPC protocols** (AWS JSON, rpcv2-cbor): Use `RpcEventStreamsUtil` helper for body wrapping and initial event
handling
- **REST protocols** (restJson1, restXml): Use `HttpBindingClientProtocol`'s built-in event streaming via
`RequestSerializer.eventEncoderFactory()` and `ResponseDeserializer.eventDecoderFactory()`

## Protocol Comparison

| Aspect | AWS JSON 1.0/1.1 | restJson1 | restXml | AWS Query |
|--------|-------------------|-----------|---------|-----------|
| Style | RPC | REST | REST | RPC |
| Request body | JSON | JSON + HTTP bindings | XML + HTTP bindings | Form URL-encoded |
| Response body | JSON | JSON + HTTP bindings | XML + HTTP bindings | XML with wrappers |
| Operation routing | `X-Amz-Target` header | HTTP method + URI | HTTP method + URI | `Action=` in body |
| `@jsonName` | No | Yes | N/A | N/A |
| `@xmlName` | N/A | N/A | Yes | Yes |
| Empty input | `{}` | Omitted | Omitted | `Action=Op&Version=V` |
| Event streaming | Yes | Yes | Yes | No |
| Required settings | `service` | `service` | `service` | `service` + `serviceVersion` |

## Key Design Patterns

1. **RPC vs REST split**: RPC protocols extend `HttpClientProtocol` directly. REST protocols extend
`HttpBindingClientProtocol` which delegates to `HttpBinding` for HTTP trait-based serialization.

2. **Shared event streaming**: All protocols use the same
`AwsEventFrame`/`AwsEventEncoderFactory`/`AwsEventDecoderFactory` infrastructure.

3. **SPI-based discovery**: All protocol factories are registered via `ClientProtocolFactory` SPI, enabling runtime
protocol selection.

4. **Error header priority**: All AWS protocols check `x-amzn-errortype` header before parsing the response body for
error type.
Loading