Configuring workload identity federation for AWS
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto the page URL.
Use AWS as a Workload Identity Provider in either of these scenarios:
- AWS outbound identity federation: Exchange an AWS STS-issued OIDC JWT from
GetWebIdentityTokenfor a short-lived OpenAI access token. - Amazon EKS: Exchange a projected Amazon EKS service account token for a short-lived OpenAI access token.
For Codex, use this page to get and inspect the AWS token. Then configure Codex workload identity to write that token to a file and point Codex to it. The service-account mapping and SDK examples on this page apply to the OpenAI API.
OpenAI supports AWS-issued OIDC JWTs from outbound identity federation and Kubernetes projected service account tokens issued by Amazon EKS. OpenAI does not support SigV4-signed requests or AWS STS temporary access key credentials as workload identity federation subject tokens.
AWS outbound identity federation
AWS outbound identity federation lets an AWS principal request a signed OIDC JWT from AWS STS and present that token to an external service. In OpenAI workload identity federation, the AWS-issued JWT is the subject token that OpenAI validates before issuing an OpenAI access token.
Setting up AWS outbound identity federation
Enable outbound identity federation for the AWS account that will issue tokens. For setup details, see the AWS guide to getting started with outbound identity federation.
aws iam enable-outbound-web-identity-federationRecord the account-specific issuer URL returned by AWS. You will configure this value as the OpenAI Workload Identity Provider issuer, and it must match the iss claim in AWS-issued tokens.
The AWS STS GetWebIdentityToken API is not available on the STS global endpoint. Configure the AWS CLI or SDK to use a regional STS endpoint.
Grant the workload permission to call sts:GetWebIdentityToken. Restrict the audience and maximum token lifetime in IAM so the AWS principal can mint only tokens intended for OpenAI. This example allows tokens for the audience https://api.openai.com/v1 with a maximum lifetime of 300 seconds:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:GetWebIdentityToken",
"Resource": "*",
"Condition": {
"ForAllValues:StringEquals": {
"sts:IdentityTokenAudience": "https://api.openai.com/v1"
},
"NumericLessThanEquals": {
"sts:DurationSeconds": 300
}
}
}
]
}Request an AWS-issued OIDC token with the same audience you will configure on the OpenAI Workload Identity Provider. Use ES384 unless your environment requires RS256 compatibility.
TOKEN=$(aws sts get-web-identity-token \
--audience "https://api.openai.com/v1" \
--signing-algorithm ES384 \
--duration-seconds 300 \
--tags Key=environment,Value=production \
Key=workload,Value=batch-ingest \
--query "WebIdentityToken" \
--output text)
export TOKENVerify the AWS-issued token
Before configuring workload identity federation, export the AWS-issued token as TOKEN, then run this script locally to inspect its claims:
const parts = process.env.TOKEN?.split(".") ?? [];
if (parts.length !== 3) {
throw new Error("Expected a compact JWT with three segments");
}
if (!/^[A-Za-z0-9_-]+$/.test(parts[1]) || parts[1].length % 4 === 1) {
throw new Error("JWT payload is not valid Base64URL");
}
const bytes = Buffer.from(parts[1], "base64url");
if (bytes.toString("base64url") !== parts[1]) {
throw new Error("JWT payload is not valid Base64URL");
}
const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
const claims = JSON.parse(decoded);
if (claims === null || Array.isArray(claims) || typeof claims !== "object") {
throw new Error("JWT payload is not a JSON object");
}
console.log(decoded);import base64
import json
import os
import re
def reject_non_json_constant(value):
raise ValueError(f"JWT payload contains non-JSON constant: {value}")
parts = os.environ.get("TOKEN", "").split(".")
if len(parts) != 3:
raise ValueError("Expected a compact JWT with three segments")
payload = parts[1]
if re.fullmatch(r"[A-Za-z0-9_-]+", payload) is None or len(payload) % 4 == 1:
raise ValueError("JWT payload is not valid Base64URL")
padded_payload = payload + "=" * (-len(payload) % 4)
decoded = base64.b64decode(padded_payload, altchars=b"-_", validate=True)
if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != payload:
raise ValueError("JWT payload is not valid Base64URL")
decoded_text = decoded.decode("utf-8")
claims = json.loads(decoded_text, parse_constant=reject_non_json_constant)
if not isinstance(claims, dict):
raise ValueError("JWT payload is not a JSON object")
print(decoded_text)package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strings"
"unicode/utf8"
)
func decodeSegment(segment string) (json.RawMessage, error) {
if !isBase64URLSegment(segment) {
return nil, fmt.Errorf("JWT segment is not valid Base64URL")
}
decoded, err := base64.RawURLEncoding.DecodeString(segment)
if err != nil {
return nil, err
}
if base64.RawURLEncoding.EncodeToString(decoded) != segment {
return nil, fmt.Errorf("JWT segment is not valid Base64URL")
}
if !utf8.Valid(decoded) {
return nil, fmt.Errorf("JWT segment is not valid UTF-8")
}
var value json.RawMessage
if err := json.Unmarshal(decoded, &value); err != nil {
return nil, err
}
if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {
return nil, fmt.Errorf("JWT segment is not a JSON object")
}
return value, nil
}
func isBase64URLSegment(segment string) bool {
if segment == "" || len(segment)%4 == 1 {
return false
}
for _, character := range segment {
if !('A' <= character && character <= 'Z') &&
!('a' <= character && character <= 'z') &&
!('0' <= character && character <= '9') &&
character != '-' &&
character != '_' {
return false
}
}
return true
}
func main() {
parts := strings.Split(os.Getenv("TOKEN"), ".")
if len(parts) != 3 {
panic("Expected a compact JWT with three segments")
}
payload, err := decodeSegment(parts[1])
if err != nil {
panic(err)
}
formatted, err := json.MarshalIndent(payload, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(formatted))
}// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public final class DecodeJwtPayloadExample {
private static final ObjectMapper JSON =
new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
private DecodeJwtPayloadExample() {}
static String decodeUtf8(byte[] bytes) throws IOException {
try {
return StandardCharsets.UTF_8
.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT)
.decode(ByteBuffer.wrap(bytes))
.toString();
} catch (CharacterCodingException exception) {
throw new IOException("JWT segment is not valid UTF-8", exception);
}
}
static String decodeSegment(String segment) throws IOException {
if (!isBase64UrlSegment(segment)) {
throw new IllegalArgumentException("JWT segment is not valid Base64URL");
}
byte[] bytes = Base64.getUrlDecoder().decode(segment);
if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {
throw new IllegalArgumentException("JWT segment is not valid Base64URL");
}
String decoded = decodeUtf8(bytes);
JsonNode value = JSON.readTree(decoded);
if (value == null || value.isMissingNode() || !value.isObject()) {
throw new IOException("JWT segment is not a JSON object");
}
return decoded;
}
static boolean isBase64UrlSegment(String segment) {
if (segment.isEmpty() || segment.length() % 4 == 1) {
return false;
}
return segment
.chars()
.allMatch(
character ->
character >= 'A' && character <= 'Z'
|| character >= 'a' && character <= 'z'
|| character >= '0' && character <= '9'
|| character == '-'
|| character == '_');
}
static String[] requireCompactJwt(String token) {
if (token == null) {
throw new IllegalArgumentException("Expected a compact JWT with three segments");
}
String[] parts = token.split("\\.", -1);
if (parts.length != 3) {
throw new IllegalArgumentException("Expected a compact JWT with three segments");
}
return parts;
}
public static void main(String[] args) throws IOException {
String[] parts = requireCompactJwt(System.getenv("TOKEN"));
System.out.println(decodeSegment(parts[1]));
}
}using System.Text;
using System.Text.Json;
static string DecodeSegment(string segment)
{
if (
segment.Length % 4 == 1 ||
segment.Any(
character =>
!(
character is >= 'A' and <= 'Z' ||
character is >= 'a' and <= 'z' ||
character is >= '0' and <= '9' ||
character is '-' or '_'
)
)
)
{
throw new FormatException("JWT segment is not valid Base64URL");
}
byte[] decoded = Convert.FromBase64String(
segment.Replace('-', '+').Replace('_', '/') +
new string('=', (4 - segment.Length % 4) % 4)
);
string canonicalSegment = Convert
.ToBase64String(decoded)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
if (canonicalSegment != segment)
{
throw new FormatException("JWT segment is not valid Base64URL");
}
string decodedJson = new UTF8Encoding(false, true).GetString(decoded);
using JsonDocument document = JsonDocument.Parse(decodedJson);
if (document.RootElement.ValueKind is not JsonValueKind.Object)
{
throw new FormatException("JWT segment is not a JSON object");
}
return decodedJson;
}
string? token = Environment.GetEnvironmentVariable("TOKEN");
if (token is null)
{
throw new InvalidOperationException(
"Expected a compact JWT with three segments"
);
}
string[] parts = token.Split('.');
if (parts.Length != 3)
{
throw new InvalidOperationException(
"Expected a compact JWT with three segments"
);
}
Console.WriteLine(DecodeSegment(parts[1]));require "base64"
require "json"
parts = ENV.fetch("TOKEN", "").split(".", -1)
raise "Expected a compact JWT with three segments" unless parts.length == 3
unless parts[1].match?(/\A[A-Za-z0-9_-]+\z/) && parts[1].length % 4 != 1
raise "JWT payload is not valid Base64URL"
end
begin
payload = Base64.urlsafe_decode64(parts[1].ljust((parts[1].length + 3) & ~3, "="))
rescue ArgumentError
raise "JWT payload is not valid Base64URL"
end
unless Base64.urlsafe_encode64(payload, padding: false) == parts[1]
raise "JWT payload is not valid Base64URL"
end
payload.force_encoding(Encoding::UTF_8)
raise "JWT payload is not valid UTF-8" unless payload.valid_encoding?
claims = JSON.parse(payload)
raise "JWT payload is not a JSON object" unless claims.is_a?(Hash)
puts(payload)This command decodes the JWT payload without verifying the token signature. Use a local decoder for production tokens, and avoid pasting production tokens into third-party tools.
A decoded AWS-issued OIDC token will look similar to:
{
"iss": "https://abc123-def456-ghi789-jkl012.tokens.sts.global.api.aws",
"aud": "https://api.openai.com/v1",
"sub": "arn:aws:iam::123456789012:role/OpenAIWifRole",
"iat": 1716235422,
"exp": 1716235722,
"jti": "jwt-id-example",
"https://sts.amazonaws.com/": {
"aws_account": "123456789012",
"source_region": "us-west-2",
"org_id": "o-exampleorgid",
"principal_tags": {
"environment": "production"
},
"request_tags": {
"environment": "production",
"workload": "batch-ingest"
}
}
}Not every AWS-issued token contains every AWS-specific claim. The claims under https://sts.amazonaws.com/ depend on the calling principal, session context, and request tags.
Verify the claims you plan to configure in OpenAI:
iss: Must match the AWS account-specific issuer URL configured in the OpenAI Workload Identity Provider.aud: Must match theGetWebIdentityTokenaudience and the OpenAI Workload Identity Provider audience.sub: Identifies the IAM principal ARN that requested the token. Prefer matching the exact role ARN.- AWS-specific claims: Use the decoded token as the source of truth before matching account, organization, principal tag, or request tag values.
Use the decoded payload to compare the token you received with the issuer, audience, and mapping values configured in OpenAI. Most configuration issues are visible in the iss, aud, and sub claims before you exchange the token.
Setting up workload identity federation
Create a Workload Identity Provider in OpenAI for the AWS account issuer, then add a service account mapping that matches stable claims from the AWS-issued token.
Configure the Workload Identity Provider first, then create the service account mapping.
Set up the Workload Identity Provider
Create the Workload Identity Provider. Set Name to a unique value, such as
aws-outbound-prod. Use Description, such asProduction AWS outbound identity federation workloads, to help admins identify the provider.Set the issuer and audience. Set OIDC Issuer URL to the AWS account-specific issuer URL returned when outbound identity federation was enabled. This value must match the token's
issclaim. Set Audience to the same audience passed toGetWebIdentityToken. In this example, that value ishttps://api.openai.com/v1.Use AWS OIDC discovery. Leave Use uploaded JWKS for token verification disabled. OpenAI uses the AWS issuer's OIDC discovery metadata and JWKS to verify the AWS-issued token.
Add attribute transformations only if you need derived mapping attributes. Raw token matching supports top-level scalar claims such as
sub,aud, andiss. AWS-specific namespaced claims are nested underhttps://sts.amazonaws.com/, so create derived attributes with CEL bracket notation before using them in mappings. For example, enteraws_environmentwith expressionassertion["https://sts.amazonaws.com/"]["principal_tags"]["environment"]to createopenai.aws_environmentfrom the decoded token example above. Verify the nested claim path in a sample token before using it; if a transformation cannot be evaluated, mapping resolution fails. Raw token claims that already start withopenai.are ignored foropenai.mapping keys unless a matching transformation is configured.
Set up the service account mapping
Create a service account mapping. Set Name to a value that is unique within the Workload Identity Provider, such as
aws-role-openai-wif. Use Description, such asProduction AWS role for OpenAI API workload, to explain which workload can use the mapping.Match the AWS principal. Set Key to
suband Value to the IAM principal ARN from the decoded token, such asarn:aws:iam::123456789012:role/OpenAIWifRole. Matching on the exactsubclaim provides the strongest isolation for AWS outbound identity federation.Add additional claim matches if needed. You can match on any available scalar claim or transformed attribute. For example, use transformed attributes derived from AWS account, organization, principal tag, or request tag claims if you need additional trust boundaries.
Choose the OpenAI target. Set Project to the OpenAI project that owns the target service account. Set Service account to the OpenAI service account the AWS workload can use, such as
aws-outbound-prod-openai-wif.Narrow API permissions if needed. Select appropriate Permissions such as
api.model.requestandapi.vector_store.readto further narrow access tokens minted from this mapping. Leave permissions blank to avoid adding a WIF-specific scope restriction; the token still authorizes as the mapped service account.
Using the token in code
Configure your OpenAI SDK client to request an AWS-issued OIDC token from AWS STS and exchange it for an OpenAI-issued access token.
Set OPENAI_WIF_AUDIENCE to the same audience configured on the OpenAI Workload Identity Provider. The subject token provider calls AWS STS GetWebIdentityToken with that audience, returns the AWS-issued JWT as the subject token, and the OpenAI SDK exchanges it for an OpenAI-issued access token.
Authenticate from an AWS-issued OIDC token
import { GetWebIdentityTokenCommand, STSClient } from "@aws-sdk/client-sts";
import OpenAI from "openai";
const identityProviderId = process.env.OPENAI_IDENTITY_PROVIDER_ID;
const serviceAccountId = process.env.OPENAI_SERVICE_ACCOUNT_ID;
const audience = process.env.OPENAI_WIF_AUDIENCE;
const awsRegion = process.env.AWS_REGION;
if (!identityProviderId || !serviceAccountId || !audience || !awsRegion) {
throw new Error(
"Set OPENAI_IDENTITY_PROVIDER_ID, OPENAI_SERVICE_ACCOUNT_ID, OPENAI_WIF_AUDIENCE, and AWS_REGION"
);
}
const wifAudience = audience;
const sts = new STSClient({ region: awsRegion });
/** @returns {import("openai/auth/index").SubjectTokenProvider} */
function awsOutboundWebIdentityTokenProvider() {
return {
tokenType: "jwt",
getToken: async () => {
const response = await sts.send(
new GetWebIdentityTokenCommand({
Audience: [wifAudience],
SigningAlgorithm: "ES384",
DurationSeconds: 300,
})
);
if (!response.WebIdentityToken) {
throw new Error("AWS STS did not return a web identity token.");
}
return response.WebIdentityToken;
},
};
}
const client = new OpenAI({
workloadIdentity: {
identityProviderId,
serviceAccountId,
provider: awsOutboundWebIdentityTokenProvider(),
},
});
const response = await client.responses.create({
model: "gpt-5.6-terra",
input: "Say hello from AWS outbound workload identity federation.",
});
console.log(response.output_text);import os
import boto3
from openai import OpenAI
from openai.auth import SubjectTokenProvider
def aws_outbound_web_identity_token_provider(audience: str) -> SubjectTokenProvider:
sts = boto3.client("sts", region_name=os.environ["AWS_REGION"])
def get_token() -> str:
response = sts.get_web_identity_token(
Audience=[audience],
SigningAlgorithm="ES384",
DurationSeconds=300,
)
token = response.get("WebIdentityToken", "")
if not token:
raise RuntimeError("AWS STS did not return a web identity token.")
return token
return {"token_type": "jwt", "get_token": get_token}
client = OpenAI(
workload_identity={
"identity_provider_id": os.environ["OPENAI_IDENTITY_PROVIDER_ID"],
"service_account_id": os.environ["OPENAI_SERVICE_ACCOUNT_ID"],
"provider": aws_outbound_web_identity_token_provider(
os.environ["OPENAI_WIF_AUDIENCE"]
),
},
)
response = client.responses.create(
model="gpt-5.6-terra",
input="Say hello from AWS outbound workload identity federation.",
)
print(response.output_text)package main
import (
"context"
"fmt"
"log"
"os"
awssdk "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/auth"
"github.com/openai/openai-go/v3/option"
"github.com/openai/openai-go/v3/responses"
)
type awsOutboundWebIdentityTokenProvider struct {
client *sts.Client
audience string
}
func (p awsOutboundWebIdentityTokenProvider) TokenType() auth.SubjectTokenType {
return auth.SubjectTokenTypeJWT
}
func (p awsOutboundWebIdentityTokenProvider) GetToken(ctx context.Context, _ auth.HTTPDoer) (string, error) {
output, err := p.client.GetWebIdentityToken(ctx, &sts.GetWebIdentityTokenInput{
Audience: []string{p.audience},
DurationSeconds: awssdk.Int32(300),
SigningAlgorithm: awssdk.String("ES384"),
})
if err != nil {
return "", &auth.SubjectTokenProviderError{
Provider: "aws-outbound",
Message: "failed to request AWS web identity token",
Cause: err,
}
}
token := awssdk.ToString(output.WebIdentityToken)
if token == "" {
return "", &auth.SubjectTokenProviderError{
Provider: "aws-outbound",
Message: "AWS STS did not return a web identity token",
}
}
return token, nil
}
func main() {
ctx := context.Background()
audience := os.Getenv("OPENAI_WIF_AUDIENCE")
if audience == "" {
log.Fatal("Set OPENAI_WIF_AUDIENCE")
}
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
log.Fatal(err)
}
client := openai.NewClient(
option.WithWorkloadIdentity(auth.WorkloadIdentity{
IdentityProviderID: os.Getenv("OPENAI_IDENTITY_PROVIDER_ID"),
ServiceAccountID: os.Getenv("OPENAI_SERVICE_ACCOUNT_ID"),
Provider: awsOutboundWebIdentityTokenProvider{
client: sts.NewFromConfig(cfg),
audience: audience,
},
}),
)
response, err := client.Responses.New(ctx, responses.ResponseNewParams{
Model: openai.ChatModelGPT4_1Mini,
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("Say hello from AWS outbound workload identity federation."),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.OutputText())
}import com.fasterxml.jackson.databind.json.JsonMapper;
import com.openai.auth.SubjectTokenProvider;
import com.openai.auth.SubjectTokenType;
import com.openai.auth.WorkloadIdentity;
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.http.HttpClient;
import com.openai.errors.SubjectTokenProviderException;
import com.openai.models.responses.ResponseCreateParams;
import java.util.concurrent.CompletableFuture;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.GetWebIdentityTokenRequest;
public final class AwsOutboundWorkloadIdentityExample {
private AwsOutboundWorkloadIdentityExample() {}
static final class AwsOutboundWebIdentityTokenProvider implements SubjectTokenProvider {
private final StsClient stsClient;
private final String audience;
AwsOutboundWebIdentityTokenProvider(StsClient stsClient, String audience) {
this.stsClient = stsClient;
this.audience = audience;
}
@Override
public SubjectTokenType tokenType() {
return SubjectTokenType.JWT;
}
@Override
public String getToken(HttpClient httpClient, JsonMapper jsonMapper) {
try {
String token =
stsClient
.getWebIdentityToken(
GetWebIdentityTokenRequest.builder()
.audience(audience)
.durationSeconds(300)
.signingAlgorithm("ES384")
.build())
.webIdentityToken();
if (token == null || token.isEmpty()) {
throw new SubjectTokenProviderException(
"aws-outbound", "AWS STS did not return a web identity token", null);
}
return token;
} catch (SubjectTokenProviderException e) {
throw e;
} catch (Exception e) {
throw new SubjectTokenProviderException(
"aws-outbound", "failed to request AWS web identity token", e);
}
}
@Override
public CompletableFuture<String> getTokenAsync(HttpClient httpClient, JsonMapper jsonMapper) {
return CompletableFuture.supplyAsync(() -> getToken(httpClient, jsonMapper));
}
}
public static void main(String[] args) {
String audience = System.getenv("OPENAI_WIF_AUDIENCE");
StsClient stsClient =
StsClient.builder().region(Region.of(System.getenv("AWS_REGION"))).build();
WorkloadIdentity workloadIdentity =
WorkloadIdentity.builder()
.identityProviderId(System.getenv("OPENAI_IDENTITY_PROVIDER_ID"))
.serviceAccountId(System.getenv("OPENAI_SERVICE_ACCOUNT_ID"))
.provider(new AwsOutboundWebIdentityTokenProvider(stsClient, audience))
.build();
OpenAIClient client = OpenAIOkHttpClient.builder().workloadIdentity(workloadIdentity).build();
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-5.6-terra")
.input("Say hello from AWS outbound workload identity federation.")
.build();
client.responses().create(params).output().stream()
.flatMap(item -> item.message().stream())
.flatMap(message -> message.content().stream())
.flatMap(content -> content.outputText().stream())
.forEach(outputText -> System.out.println(outputText.text()));
}
}require "aws-sdk-sts"
require "openai"
class AwsOutboundWebIdentityTokenProvider
include OpenAI::Auth::SubjectTokenProvider
def initialize(audience:, sts_client:)
@audience = audience
@sts_client = sts_client
end
def token_type
OpenAI::Auth::TokenType::JWT
end
def get_token
response = @sts_client.get_web_identity_token(
audience: [@audience],
signing_algorithm: "ES384",
duration_seconds: 300
)
token = response.web_identity_token.to_s
if token.empty?
raise OpenAI::Errors::SubjectTokenProviderError.new(
message: "AWS STS did not return a web identity token",
provider: "aws-outbound"
)
end
token
rescue Aws::STS::Errors::ServiceError => e
raise OpenAI::Errors::SubjectTokenProviderError.new(
message: "Failed to request AWS web identity token: #{e.message}",
provider: "aws-outbound",
cause: e
)
end
end
provider = AwsOutboundWebIdentityTokenProvider.new(
audience: ENV.fetch("OPENAI_WIF_AUDIENCE"),
sts_client: Aws::STS::Client.new(region: ENV.fetch("AWS_REGION"))
)
workload_identity = OpenAI::Auth::WorkloadIdentity.new(
identity_provider_id: ENV.fetch("OPENAI_IDENTITY_PROVIDER_ID"),
service_account_id: ENV.fetch("OPENAI_SERVICE_ACCOUNT_ID"),
provider: provider
)
client = OpenAI::Client.new(workload_identity: workload_identity)
response = client.responses.create(
model: "gpt-5.6-terra",
input: "Say hello from AWS outbound workload identity federation."
)
puts(response.output_text)Amazon EKS projected service account tokens
Use Amazon EKS as a Workload Identity Provider by exchanging an EKS-issued projected service account token for a short-lived OpenAI access token.
Setting up EKS
Use a Kubernetes ServiceAccount for the EKS workload that needs to call the OpenAI API. If you do not already have one, create it:
kubectl create serviceaccount openai-wif --namespace default