# How Macro Microservices Handle Authentication with JWT Tokens

> Learn how Macro's microservices authenticate users with HS256-signed JWT tokens. Discover secure secret management with AWS Secrets Manager and streamlined authorization using MacroUserId.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: how-to-guide
- Published: 2026-08-14

---

**Macro's microservice architecture uses HS256-signed JWT tokens validated by shared Axum middleware, with secrets fetched from AWS Secrets Manager at startup and user identity extracted into strongly-typed `MacroUserId` for downstream authorization.**

Every service in the Macro repository implements the same authentication flow through reusable crates. This design ensures stateless, consistent JWT validation across the entire service mesh without duplicating security logic.

## JWT Authentication Architecture Overview

The authentication system spans two core crates and multiple service implementations. According to the macro-inc/macro source code, the flow depends on environment-driven configuration, a trait-based validator abstraction, and Axum middleware integration.

### Key Components

- **[`crates/decode_jwt/src/lib.rs`](https://github.com/macro-inc/macro/blob/main/crates/decode_jwt/src/lib.rs)** — Token structures (`MacroAccessToken`, `DocumentPermissionsToken`) and decoding primitives
- **[`crates/macro_authorization/src/outbound/macro_auth.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_authorization/src/outbound/macro_auth.rs)** — `MacroAuthJwtValidator` implementation
- **[`crates/macro_authorization/src/lib.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_authorization/src/lib.rs)** — Public exports and the `JwtValidator` trait
- **Service [`main.rs`](https://github.com/macro-inc/macro/blob/main/main.rs) files** — Bootstrap and middleware injection

## Environment-Driven JWT Configuration

Each service initializes authentication by building a `JwtValidationArgs` struct. The async constructor `JwtValidationArgs::new_with_secret_manager` (defined in [`crates/decode_jwt/src/lib.rs`](https://github.com/macro-inc/macro/blob/main/crates/decode_jwt/src/lib.rs) at lines 31-35) fetches:

- `jwt_secret` — The HS256 signing key
- `audience` — Token audience claim
- `issuer` — Token issuer claim

In production, credentials come from **AWS Secrets Manager**. In development, they load from local environment variables.

```rust
// Typically in each service's main.rs
let jwt_args = JwtValidationArgs::new_with_secret_manager(
    environment.clone(),
    &secretsmanager_client,
)
.await
.context("failed to build jwt validation args")?;

```

This pattern appears in [`services/static_file_service/src/main.rs`](https://github.com/macro-inc/macro/blob/main/services/static_file_service/src/main.rs) and [`services/document_storage_service/src/main.rs`](https://github.com/macro-inc/macro/blob/main/services/document_storage_service/src/main.rs), ensuring every service bootstraps authentication identically.

## The JwtValidator Trait and MacroAuthJwtValidator

The `macro_authorization` crate defines a reusable validation layer. In [`crates/macro_authorization/src/outbound/macro_auth.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_authorization/src/outbound/macro_auth.rs), the `MacroAuthJwtValidator::new` method accepts `JwtValidationArgs` and stores:

- The decoded JWT secret
- Expected audience and issuer values

This validator implements the `JwtValidator` trait, enabling dependency injection and test mocking.

```rust
let validator = MacroAuthJwtValidator::new(jwt_args);

```

The validator is then wrapped into `MacroAuthorizationState` and injected into Axum routers as application state.

## Token Validation in Axum Middleware

When requests arrive, middleware extracts the **Bearer token** and delegates to `MacroAuthJwtValidator::validate`. The implementation in [`services/mcp_auth_proxy/src/inbound/middleware.rs`](https://github.com/macro-inc/macro/blob/main/services/mcp_auth_proxy/src/inbound/middleware.rs) (lines 73-89) demonstrates this pattern:

```rust
async fn auth_middleware(
    State(jwt_args): axum::extract::State<JwtValidationArgs>,
    axum::extract::TypedHeader(bearer): axum::extract::TypedHeader<Bearer>,
) -> Result<JwtToken, MacroAuthError> {
    handler(&jwt_args, &bearer.token())
}

```

Internally, `validate_macro_access_token_inner` (in [`decode_jwt.rs`](https://github.com/macro-inc/macro/blob/main/decode_jwt.rs), lines 66-78) calls `jsonwebtoken::decode` with:

- **Algorithm**: `HS256`
- **Audience validation**: Matches configured audience
- **Issuer validation**: Matches configured issuer

### Error Handling

The validator returns specific error variants for debugging and metrics:

- `MacroAuthError::JwtExpired` — Token `exp` claim is in the past
- `MacroAuthError::JwtValidationFailed` — Signature mismatch, audience/issuer mismatch, or malformed token

## Extracting User Identity from Validated Tokens

After signature verification, the token payload deserializes into `MacroAccessToken` (defined in [`decode_jwt.rs`](https://github.com/macro-inc/macro/blob/main/decode_jwt.rs), lines 93-112). This struct contains:

```rust
pub struct MacroAccessToken {
    pub macro_user_id: String,
    // ... additional claims
}

```

The `macro_user_id` field parses into a strongly-typed `MacroUserId` that downstream handlers use for authorization decisions. The `MacroAuthorizationExtractor` helper (used across all services) provides convenient access:

```rust
async fn protected_endpoint(
    State(auth_state): State<MacroAuthorizationState>,
    extractor: MacroAuthorizationExtractor,
) -> Result<Json<...>, ApiError> {
    let user_id = extractor.user_id()?;
    // `user_id` is a `MacroUserId` derived from the JWT
    ...
}

```

## Service-Specific JWT Variants: Document Permissions

Some services extend the pattern for fine-grained access control. The **document storage service** implements a `DocumentPermissionsToken` for temporary, scoped access to stored files.

### Token Creation

In [`services/document_storage_service/src/api/documents/permissions_token/create_permission_token.rs`](https://github.com/macro-inc/macro/blob/main/services/document_storage_service/src/api/documents/permissions_token/create_permission_token.rs), the service creates permission tokens using:

- The same `jwt_secret` from `config.document_permission_jwt`
- A distinct claim structure encoding document IDs and access levels

### Token Validation

The counterpart in [`services/document_storage_service/src/api/documents/permissions_token/validate_permissions_token.rs`](https://github.com/macro-inc/macro/blob/main/services/document_storage_service/src/api/documents/permissions_token/validate_permissions_token.rs) validates these tokens using identical cryptographic primitives but different deserialization logic.

## Why This Design Scales

**Stateless validation** — No session store or inter-service authentication calls. Each service independently verifies tokens using locally cached secrets.

**Centralized secret management** — Secrets fetch once at startup via `new_with_secret_manager`. No runtime secret retrieval latency or failure modes.

**Uniform middleware** — Every service uses `MacroAuthJwtValidator` and `MacroAuthorizationExtractor`. Security fixes apply globally by updating the shared crate.

**Strong typing** — Raw JWT claims convert to `MacroUserId` at the trust boundary, preventing string-typed identity bugs in business logic.

## Summary

- Macro microservices authenticate with **HS256-signed JWT tokens** validated by shared Axum middleware
- `JwtValidationArgs::new_with_secret_manager` sources secrets from AWS Secrets Manager or environment variables at startup
- `MacroAuthJwtValidator` (in [`crates/macro_authorization/src/outbound/macro_auth.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_authorization/src/outbound/macro_auth.rs)) provides the concrete validation implementation
- Validated tokens deserialize into `MacroAccessToken` with strongly-typed `MacroUserId` extraction
- Services like document storage extend the pattern with service-specific permission tokens using the same cryptographic foundation
- The architecture is **stateless**, **centrally configured**, and **uniformly implemented** across all services

## Frequently Asked Questions

### What JWT algorithm does Macro use for token signing?

Macro uses **HS256** (HMAC with SHA-256). The `jsonwebtoken::decode` call in `validate_macro_access_token_inner` explicitly specifies this algorithm, and secrets are symmetric keys fetched from AWS Secrets Manager or environment variables.

### How does Macro handle JWT expiration?

The validator returns `MacroAuthError::JwtExpired` when a token's `exp` claim is in the past. This error propagates through the Axum middleware and converts to an appropriate HTTP 401 response. Services do not implement custom expiration handling—it's uniform across the codebase.

### Can services use different JWT secrets?

Yes. Services read from `jwt_secret` (for standard access tokens) or service-specific config like `config.document_permission_jwt` (for document permissions in the storage service). All secrets still originate from the same secret manager infrastructure, but different keys can be configured per-service and per-token-type.

### Is the JWT validation logic testable?

Yes. The `JwtValidator` trait abstraction in [`crates/macro_authorization/src/lib.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_authorization/src/lib.rs) allows mocking validators in unit tests. Services inject `MacroAuthJwtValidator` through Axum's state system, so test suites can substitute stub implementations without HTTP middleware changes.