iTranslated by AI
Testing API Gateway Authentication with JWTs Issued by AWS IAM | Outbound Identity Federation in Practice
Introduction
In November 2025, AWS announced a new feature called IAM Outbound Identity Federation. This feature allows AWS IAM principals (Roles or Users) to obtain short-lived JWTs and use them for authentication with external services.
In this article, I tried authenticating with API Gateway HTTP API's JWT authorizer using a JWT issued by this feature.
What is IAM Outbound Identity Federation?
IAM Outbound Identity Federation is a feature where AWS IAM principals (Roles or Users) obtain short-lived JWT tokens and use those tokens to authenticate to external services.
Main features:
- Obtain JWTs via the AWS STS
GetWebIdentityTokenAPI - Tokens include information such as the originating AWS account and principal details
- External services can verify tokens using a JWKS (JSON Web Key Set) endpoint
- Token duration is between 60 to 3600 seconds (1 minute to 1 hour), with a default of 300 seconds (5 minutes)
Architecture
The configuration of the system to be built this time is as follows:
Components:
- API Gateway (HTTP API): Verifies tokens using a JWT authorizer
- Lambda Function: Backend handler
- IAM Issuer URL: Provides the JWKS endpoint for token verification
Implementation Steps
Prerequisites
- AWS CLI is installed and properly configured
- AWS CDK is installed
- Node.js/TypeScript environment
Obtaining the Issuer URL
First, enable IAM Outbound Identity Federation and obtain the Issuer URL.
- Open "Account settings" in the IAM console
- Enable the feature in the "Outbound Identity Federation" section
- Note the displayed Issuer URL (format:
https://xxxx.tokens.sts.global.api.aws)
Deploying with CDK
Deploy the API Gateway and Lambda function using CDK.
import { CfnOutput, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib";
import { HttpApi } from "aws-cdk-lib/aws-apigatewayv2";
import { HttpJwtAuthorizer } from "aws-cdk-lib/aws-apigatewayv2-authorizers";
import { HttpLambdaIntegration } from "aws-cdk-lib/aws-apigatewayv2-integrations";
import { HttpMethod } from "aws-cdk-lib/aws-events";
import { Runtime } from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs";
import { Construct } from "constructs";
export class Gekko001Stack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const handler = new NodejsFunction(this, 'HelloWorldHandler', {
runtime: Runtime.NODEJS_LATEST,
entry: `${__dirname}/hello-handler.ts`,
handler: 'handler',
logGroup: new LogGroup(this, 'HelloWorldLogGroup', {
logGroupName: '/gekko-001/hello-world',
removalPolicy: RemovalPolicy.DESTROY,
retention: RetentionDays.ONE_DAY,
}),
});
const apiGw = new HttpApi(this, 'ApiGw');
const integration = new HttpLambdaIntegration('HelloWorldIntegration', handler);
// JWT Authorizer settings
apiGw.addRoutes({
methods: [HttpMethod.GET],
path: '/jwt/hello',
integration: integration,
authorizer: new HttpJwtAuthorizer('IamJwtAuthorizer',
'https://YOUR-ISSUER-ID.tokens.sts.global.api.aws', {
jwtAudience: [apiGw.httpApiName!],
}),
});
new CfnOutput(this, 'ApiGwUrl', {
value: apiGw.apiEndpoint,
});
new CfnOutput(this, 'ApiGwName', {
value: apiGw.httpApiName!,
});
}
}
Lambda function handler code:
import { APIGatewayProxyEventV2, Context } from 'aws-lambda';
export const handler = async (event: APIGatewayProxyEventV2, context: Context) => {
return {
statusCode: 200,
body: JSON.stringify({
message: 'Hello, World!',
event: event,
context: context
}),
};
}
Key points:
- Specify the IAM Issuer URL in
HttpJwtAuthorizer - Set the API Gateway name in
jwtAudience. This will also be used as the value specified when obtaining the token from IAM.
Deployment:
cdk deploy
Obtaining the token and accessing the API
Here is a script to obtain a token and access the API Gateway:
#!/bin/bash
if [ -z "$1" ]; then
echo "Usage: $0 <URL>" >&2
exit 1
fi
TARGET_URL="$1"
# Obtain JWT token
JWT_TOKEN=$(aws sts get-web-identity-token \
--audience ApiGw \
--signing-algorithm RS256 \
--query 'WebIdentityToken' \
--output text)
# Access API Gateway
curl -s -X GET "$TARGET_URL" \
-H "Authorization: Bearer $JWT_TOKEN"
Execution:
./scripts/jwt-get.sh https://YOUR-API-ID.execute-api.REGION.amazonaws.com/jwt/hello
Options used in the get-web-identity-token command within the script:
-
--audience ApiGw: The value set in the JWT'saudclaim (must match thejwtAudiencesetting of the authorizer). -
--signing-algorithm RS256: The signing algorithm (can be selected from RS256=RSA or ES384=ECDSA). - The token's validity period defaults to 300 seconds (5 minutes) (can be specified between 60 to 3600 seconds with
--duration-seconds).
Operation Check
If the access is successful, you will receive a response like this:
{
"message": "Hello, World!",
"event": {
"version": "2.0",
"routeKey": "GET /jwt/hello",
"requestContext": {
"authorizer": {
"jwt": {
"claims": {
"aud": "ApiGw",
"sub": "arn:aws:iam::ACCOUNT_ID:role/...",
"iss": "https://xxxx.tokens.sts.global.api.aws",
"exp": "1763824092",
"iat": "1763823192"
}
}
}
}
}
}
Within the Lambda function, you can access the token's claim information (issuer, subject, expiration, etc.) from event.requestContext.authorizer.jwt.claims.
Access Control via IAM
Required Permissions
To issue a token, the IAM principal needs the sts:GetWebIdentityToken permission.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:GetWebIdentityToken",
"Resource": "*"
}
]
}
Restrictions Using Condition Keys
You can further restrict token issuance using the following condition keys:
| Condition Key | Description | Example |
|---|---|---|
sts:IdentityTokenAudience |
Restricts the audiences that can be issued | "StringEquals": {"sts:IdentityTokenAudience": "ApiGw"} |
sts:DurationSeconds |
Restricts the token validity period | "NumericLessThanEquals": {"sts:DurationSeconds": "3600"} |
sts:SigningAlgorithm |
Restricts the signing algorithm | "StringEquals": {"sts:SigningAlgorithm": "RS256"} |
Practical Policy Example
An example that allows only specific audiences and restricts the validity period:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:GetWebIdentityToken",
"Resource": "*",
"Condition": {
"ForAnyValue:StringEquals": {
'sts:IdentityTokenAudience': ["ApiGw", "ApiGwProd"]
}
"NumericLessThanEquals": {
"sts:DurationSeconds": "1800"
}
}
}
]
}
With this policy:
- Only tokens with the audience
ApiGworApiGwProdcan be issued. - The token validity period is limited to a maximum of 30 minutes (1800 seconds).
Comparison with Other Solutions
We compare three authentication methods for API Gateway: IAM Outbound Federation, IAM authorizer, and Cognito.
| Method | Client Implementation | Portability | Complexity | Typical Use Case |
|---|---|---|---|---|
| IAM Outbound Federation | Obtain token with aws sts get-web-identity-token → add to HTTP header |
High (Standard JWT) | Low | M2M communication, testing environments, prototyping |
| IAM Authorizer | SigV4 signature for the entire request (specialized tools required) | Low (AWS specific) | High | Communication between internal AWS services |
| Cognito User Pools | OAuth 2.0 / OIDC flow | Medium (Industry standard) | Medium | End-user authentication, full-scale apps |
Detailed Comparison
vs IAM Authorizer
You might wonder, "If I'm controlling token issuance with IAM anyway, isn't using an IAM authorizer on the API Gateway side the same thing?"
Key difference: Complexity of client-side implementation
| Item | IAM Outbound Federation (JWT) | IAM Authorizer |
|---|---|---|
| Authentication Method | JWT Token | SigV4 Signature |
| Client Implementation | Add Authorization: Bearer <token> header |
Sign the entire request (headers, path, query, body) |
| Required Tools | AWS CLI/SDK only | Specialized tools like awscurl or complex implementation |
| Portability | High (Standard HTTP/JWT) | Low (AWS specific) |
| External Service Integration | Easy | Difficult |
This difference becomes especially important when integrating with external services.
vs Cognito User Pools
One might wonder, "Why not just use Cognito?" While Cognito is indeed an excellent choice, you can choose between them based on the use case.
| Item | IAM Outbound Identity Federation | Cognito User Pools |
|---|---|---|
| Setup | ||
| Required Resources | None (IAM settings only) | User Pool, App Client, Resource Server, etc. |
| Initial Setup Effort | Minimal | Moderate |
| Authentication Features | ||
| Access Control | Audience-based (Simple) | Scope-based (Flexible) |
| Custom Claims | Can be added via request tags | Can be added flexibly |
| User Management | None | Sign-up, password reset, etc. |
| Auth Flows | Token acquisition only | OAuth 2.0, SAML, Social login, etc. |
| Client Implementation | ||
| Secret Management | Not required | Client ID, secrets, etc. |
| Implementation Complexity | Simple | Moderate |
Recommended Use Cases:
- IAM Method: M2M communication within AWS, prototyping, simple authentication requirements, testing environments.
- Cognito: End-user authentication, complex permission management, full-scale applications, rich authentication flows.
⚠️ Notes on Production Use
Deviation from Design Intent
Important: This feature is originally designed for AWS workloads to access external services. Using it with API Gateway as shown here is a use case that differs from the intended design.
The official documentation also assumes use with external services (third-party clouds, SaaS, on-premises applications).
Recommended Use Cases
When considering use in a production environment, the following use cases are suitable:
Suitable:
- Temporary authentication between microservices within AWS (prototyping)
- Simple authentication in development and testing environments
- Outbound communication to external services (original purpose)
Not Suitable:
- Full-scale end-user applications
- Systems requiring complex permission management
- Cases where scope-based access control is required
Security Considerations
Points to note when using in a production environment:
-
Token Validity Period
- Adjust the default 5 minutes according to the application.
- Set to the minimum necessary duration.
-
Audience Design
- Use a different audience for each API or service.
- Avoid using wildcards or generic values.
-
IAM Policy Hardening
- Restrict token issuance using condition keys.
- Apply the principle of least privilege.
-
Auditing and Monitoring
- Record token issuance in CloudTrail.
- Detect abnormal token issuance patterns.
Summary
In this article, we explored how to use JWTs issued by AWS IAM Outbound Identity Federation to authenticate with an API Gateway JWT authorizer.
Key Findings:
- JWTs issued by IAM can be verified using the API Gateway JWT authorizer.
- The implementation is simple and can be treated as a standard JWT authentication flow.
- Token issuance can be finely controlled using IAM policies.
Applicable Scenes:
- Lightweight authentication solutions for M2M communication within AWS.
- Use in prototyping or development environments.
- Simple use cases where the extensive features of Cognito are not required.
Points to Note:
- This feature is originally intended for accessing external services, so using it in this way deviates from the design intent.
- Use cases in production environments should be carefully considered.
- Consider using Cognito if scope-based permission management is required.
While the original purpose of this feature is to enable access to external services, I felt there is potential for applying it to access control between services running on AWS. I look forward to exploring integration patterns with other AWS services in the future.
Discussion