iTranslated by AI
Implementing Spring Boot Microservice Authentication with IAM-Issued JWTs: No Secrets or Cognito Required
Introduction
In November 2025, AWS released AWS IAM Outbound Identity Federation (STS GetWebIdentityToken API). This allows AWS workloads to securely communicate their identity to external services using JWTs.
Previously, I verified how to perform authentication with API Gateway using JWTs issued by this feature. This time, I will verify how to protect communication between Spring Boot applications running on ECS using this same feature.
Benefits of This Implementation
- No Secret Management Required: No need for storing, rotating, or auditing Client Secrets. Zero leakage risk.
-
IAM-based Access Control: Flexible access management on a per-role basis. Token issuance can be finely controlled using IAM condition keys (
sts:IdentityTokenAudience,sts:SigningAlgorithm, etc.). - No Additional Resources or Costs: No need for extra resources like Secrets Manager. There are no additional charges for the feature itself (though standard charges for existing infrastructure like network transfer, ECS, and ALB still apply).
- Standard Implementation: Can be integrated with Spring Security OAuth2 Resource Server.
Architecture Overview
In this implementation, we will build two Spring Boot applications running on ECS Fargate.
System Configuration Diagram
Sequence Diagram
Key Points of the Configuration:
Implementation Points
API Server-side Implementation
Verification of the received token is handled within the Spring Security framework.
SecurityConfig.java:
@Configuration
public class SecurityConfig {
private final AppSecurityProperties props;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.decoder(jwtDecoder())));
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(this.props.getIssuerUri());
OAuth2TokenValidator<Jwt> audienceValidator = new JwtAudienceValidator(this.props.getAllowedAudiences());
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(this.props.getIssuerUri());
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
}
JwtAudienceValidator.java:
public class JwtAudienceValidator implements OAuth2TokenValidator<Jwt> {
private final List<String> allowedAudiences;
public JwtAudienceValidator(List<String> allowedAudiences) {
this.allowedAudiences = allowedAudiences;
}
@Override
public OAuth2TokenValidatorResult validate(final Jwt token) {
List<String> tokenAudiences = token.getAudience();
for (String audience : tokenAudiences) {
if (allowedAudiences.contains(audience)) {
return OAuth2TokenValidatorResult.success();
}
}
return OAuth2TokenValidatorResult
.failure(new OAuth2Error("invalid_token", "The required audience is missing", null));
}
}
API Client-side Implementation
We adopt a typical communication method where the token is attached to the Authorization header when sending requests via WebClient.
STSWebIdentityTokenService.java:
@Service
public class STSWebIdentityTokenService {
@Autowired
private DownStreamProperties downStreamProperties;
private final StsClient stsClient;
public String getJwt() {
return this.stsClient.getWebIdentityToken(
GetWebIdentityTokenRequest.builder()
.audience(this.downStreamProperties.getAudience())
.signingAlgorithm("RS256")
.durationSeconds(60)
.build())
.webIdentityToken();
}
}
Usage with WebClient:
webClient.get()
.uri("/hello")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + tokenService.getJwt())
.retrieve()
.bodyToMono(String.class);
Infrastructure Configuration
We define the necessary resources using the AWS CDK.
IAM Permission Configuration (with Conditions):
const issuerUri = new AwsCustomResource(this, 'GetIamIssuerUri', {
onCreate: {
service: 'iam',
action: 'GetOutboundWebIdentityFederationInfo',
parameters: {},
physicalResourceId: PhysicalResourceId.fromResponse('IssuerIdentifier'),
},
policy: AwsCustomResourcePolicy.fromSdkCalls({ resources: AwsCustomResourcePolicy.ANY_RESOURCE }),
}).getResponseField('IssuerIdentifier');
const audience = 'com.mahitotsu.gekko002.api';
// Grant token issuance permission with conditions
clientTaskDef.taskRole.addToPrincipalPolicy(new PolicyStatement({
effect: Effect.ALLOW,
actions: ['sts:GetWebIdentityToken'],
resources: ['*'],
conditions: {
'ForAnyValue:StringEquals': {
'sts:IdentityTokenAudience': [audience]
}
},
}));
Injection of Application Settings:
// Server container
serverTaskDef.addContainer('ServerContainer', {
image: ContainerImage.fromAsset('../api-server'),
environment: {
'APP_SECURITY_ISSUER_URI': issuerUri,
'APP_SECURITY_ALLOWED_AUDIENCES': audience
},
});
// Client container
clientTaskDef.addContainer('ClientContainer', {
image: ContainerImage.fromAsset('../api-client'),
environment: {
'DOWNSTREAM_ENDPOINT': `http://${serverName}.${localName}:${servicePort}/hello`,
'DOWNSTREAM_AUDIENCE': audience,
},
});
Verification Results
As a result of the verification, it was found that all the intended benefits could be achieved.
Additionally, through the verification process, the following extra benefits were confirmed due to the flexibility of permission management:
- Token issuance in CloudShell and verification using curl, etc., was possible → This diversifies the methods available for testing and debugging.
- Account-specific Issuers could be retrieved via API → Facilitates Infrastructure as Code (IaC).
TOKEN=$(aws sts get-web-identity-token \
--audience com.mahitotsu.gekko002.api \
--signing-algorithm RS256 \
--duration-seconds 60 \
--query 'WebIdentityToken' \
--output text)
curl -H "Authorization: Bearer $TOKEN" \
http://api-server.srv.local/hello
Future Possibilities
Since Outbound Web Identity Tokens can be easily obtained as long as you have IAM role permissions, further expansion of use cases can be expected.
- Lambda and EC2 can easily participate in this access control mechanism — participation of API Gateway has already been verified
- Test scripts running within CI/CD pipelines can also target traffic that requires JWTs for testing
Limitations
AWS IAM Outbound Identity Federation is a mechanism for conveying the identity of AWS workloads to external services and is not suitable for user authentication or detailed authorization.
Summary
AWS IAM Outbound Identity Federation (the GetWebIdentityToken API) has the potential to be a breakthrough feature that solves the challenges inherent in traditional microservice authentication.
Comparison with Traditional Approaches
| Challenge | Traditional Approach | Outbound Identity Federation |
|---|---|---|
| Cross-region communication | Impossible with Security Groups; requires CIDR blocks | Achievable via JWT |
| Cross-account communication | Requires VPC Peering or complex configurations | Achievable via JWT |
| Via Load Balancer | Requires workarounds like custom headers | Achievable via standard Authorization headers |
| Secret Management | Requires Secrets Manager, etc. | Achievable with IAM roles only |
| Additional Cost | Cognito, Secrets Manager, etc. | Completely free |
| Implementation Complexity | Requires service-specific implementations | Achievable via standard Spring Security features |
Core Value of This Implementation:
- Transcending Network Boundaries: Not constrained by physical boundaries such as VPCs or regions.
- Achieving Defense-in-Depth: In addition to API Gateway, authentication is performed at each microservice.
- Minimizing Operational Overhead: Managed entirely through IAM permission management.
While traditional best practices recommended "defense-in-depth," achieving it often required significant resources and costs. In contrast, this implementation simultaneously achieves simplicity, security, and cost-efficiency.
When used in appropriate use cases (especially workload-to-workload communication), it is a powerful feature that enables simple, secure, and low-cost authentication. We can expect to see its use expanded to Lambda, EC2, and across different AWS accounts in the future.
Discussion