How Macro Microservices Handle Authentication with JWT Tokens

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

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 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.

// 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 and 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, 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.

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 (lines 73-89) demonstrates this pattern:

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, 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, lines 93-112). This struct contains:

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:

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, 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 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) 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →