Service account tokens are the cornerstone of pod authentication in Kubernetes. With the introduction of projected service account tokens, Kubernetes has significantly improved security and flexibility in how pods authenticate to the API server and external services.
What Are Projected Service Account Tokens?
Projected service account tokens are time-bound, audience-scoped JSON Web Tokens (JWTs) that replace the legacy non-expiring service account tokens. They provide enhanced security through:
- Time-bound expiration: Tokens automatically expire and are rotated
- Audience binding: Tokens can be scoped to specific audiences
- Automatic rotation: The kubelet automatically refreshes tokens before expiration
The Problem with Legacy Service Account Tokens
Before projected tokens, Kubernetes used legacy service account tokens that had several security limitations:
- Never expire: Once created, they remain valid indefinitely unless manually revoked
- No audience restriction: Can be used to authenticate to any service that accepts them
- Stored as Secrets: Persisted in etcd, increasing the attack surface
- Broad scope: If compromised, provide unrestricted access to the API server
- Manual rotation: Required manual intervention to refresh or rotate
These limitations meant that if a token was leaked or a pod was compromised, attackers could potentially maintain persistent access to your cluster. Projected tokens solve these problems by being short-lived, automatically rotated, and scoped to specific audiences.

How Projected Tokens Work
Understanding the TokenRequest API
The TokenRequest API is a Kubernetes API (not provided by cloud providers) that generates service account tokens on-demand. It’s part of the core Kubernetes API server and was introduced in Kubernetes 1.12 (stable in 1.20).
Key characteristics:
- Endpoint: /api/v1/namespaces/{namespace}/serviceaccounts/{name}/token
- Purpose: Creates short-lived, audience-bound tokens for service accounts
- Parameters: Accepts expiration time and audience claims
- Signature: Tokens are signed by the Kubernetes API server’s private key
When you use a projected volume, the kubelet automatically calls this API on your behalf to request tokens, eliminating the need for manual token management.
What is a Projected Volume?
A projected volume is a special volume type in Kubernetes that can project (combine) multiple volume sources into a single directory. Think of it as a way to mount different types of data into your pod from various sources.
Common sources that can be projected:
- serviceAccountToken: Dynamically generated tokens via TokenRequest API
- configMap: Configuration data
- secret: Sensitive data
- downwardAPI: Pod metadata
For service account tokens, projected volumes enable the kubelet to:
- Request fresh tokens from the TokenRequest API
- Automatically refresh tokens before expiration
- Mount tokens as files in the pod’s filesystem
- Handle all the complexity of token lifecycle management
This is different from the legacy approach where tokens were stored as static Secrets and mounted directly.
Token Generation Flow
Projected tokens use the TokenRequest API to generate short-lived tokens on-demand. Here’s the typical flow:

Basic Configuration
Here’s a simple example of configuring a projected service account token:
apiVersion: v1kind: Podmetadata: name: token-demospec: serviceAccountName: my-service-account containers: - name: app image: nginx volumeMounts: - name: token mountPath: /var/run/secrets/tokens readOnly: true volumes: - name: token projected: sources: - serviceAccountToken: path: token expirationSeconds: 3600 audience: my-app
Using Projected Tokens with AKS (Azure Kubernetes Service)
AKS leverages projected tokens for Workload Identity, enabling pods to authenticate to Azure services without storing credentials.
Azure-Side Configuration
Before using Workload Identity in AKS, you need to set up the Azure side:
# 1. Create an Azure AD application (or Managed Identity)az ad sp create-for-rbac --name "myapp-workload-identity"# 2. Get the application's client IDexport APPLICATION_CLIENT_ID="<your-client-id>"# 3. Create federated identity credential that trusts your AKS clusteraz ad app federated-credential create \ --id $APPLICATION_CLIENT_ID \ --parameters '{ "name": "myapp-federated-credential", "issuer": "https://oidc.prod-aks.azure.com/<tenant-id>/<cluster-oidc-issuer-id>/", "subject": "system:serviceaccount:default:workload-identity-sa", "audiences": ["api://AzureADTokenExchange"] }'# 4. Assign Azure RBAC roles to the applicationaz role assignment create \ --assignee $APPLICATION_CLIENT_ID \ --role "Storage Blob Data Contributor" \ --scope "/subscriptions/<subscription-id>/resourceGroups/<rg-name>/providers/Microsoft.Storage/storageAccounts/<storage-account>"
Key Configuration Points:
- Issuer: Your AKS cluster’s OIDC issuer URL (unique per cluster)
- Subject: Must match the format
system:serviceaccount:<namespace>:<service-account-name> - Audiences: Must be
api://AzureADTokenExchangefor Workload Identity
AKS Workload Identity Setup
apiVersion: v1kind: ServiceAccountmetadata: name: workload-identity-sa namespace: default annotations: azure.workload.identity/client-id: "YOUR_AZURE_CLIENT_ID"apiVersion: v1kind: Podmetadata: name: aks-workload-identity-demo namespace: default labels: azure.workload.identity/use: "true" # This label triggers the webhook to inject volumesspec: serviceAccountName: workload-identity-sa containers: - name: app image: mcr.microsoft.com/azure-cli command: ["sleep", "infinity"] # Note: The following are automatically injected by the AKS Workload Identity webhook # when the pod has the label "azure.workload.identity/use: true": # # Environment variables: # - AZURE_CLIENT_ID # - AZURE_TENANT_ID # - AZURE_FEDERATED_TOKEN_FILE # - AZURE_AUTHORITY_HOST # # Volume mounts: # - name: azure-identity-token # mountPath: /var/run/secrets/azure/tokens # readOnly: true # # Volumes: # - name: azure-identity-token # projected: # sources: # - serviceAccountToken: # path: azure-identity-token # expirationSeconds: 3600 # audience: api://AzureADTokenExchange
Important: In practice, when using AKS Workload Identity, you typically only need to:
- Annotate your service account with
azure.workload.identity/client-id - Add the label
azure.workload.identity/use: "true"to your pod - Reference that service account in your pod spec
The pod spec would look like this:
apiVersion: v1kind: Podmetadata: name: aks-workload-identity-demo namespace: default labels: azure.workload.identity/use: "true"spec: serviceAccountName: workload-identity-sa containers: - name: app image: mcr.microsoft.com/azure-cli command: ["sleep", "infinity"] # Everything else is auto-injected!
AKS will automatically inject the environment variables, volume mounts, and projected volumes for you through its mutating admission webhook.
How it works in AKS:

Using Projected Tokens with EKS (Elastic Kubernetes Service)
EKS uses projected tokens for IAM Roles for Service Accounts (IRSA), allowing pods to assume AWS IAM roles.
AWS-Side Configuration
Before using IRSA in EKS, you need to configure AWS IAM:
# 1. Get your EKS cluster's OIDC provider URLaws eks describe-cluster --name my-cluster --query "cluster.identity.oidc.issuer" --output text# Output: https://oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE# 2. Create an IAM OIDC identity provider for your cluster# Note: If you created your cluster with eksctl or with OIDC enabled, this may already exist# You can verify with: aws iam list-open-id-connect-providerseksctl utils associate-iam-oidc-provider --cluster my-cluster --approve# 3. Create an IAM policy for S3 accesscat > s3-policy.json <<EOF{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*" ] } ]}EOFaws iam create-policy --policy-name S3AccessPolicy --policy-document file://s3-policy.json# 4. Create an IAM role with a trust policy that allows the service accountcat > trust-policy.json <<EOF{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:default:s3-access-sa", "oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com" } } } ]}EOFaws iam create-role --role-name s3-access-role --assume-role-policy-document file://trust-policy.json# 5. Attach the policy to the roleaws iam attach-role-policy \ --role-name s3-access-role \ --policy-arn arn:aws:iam::ACCOUNT_ID:policy/S3AccessPolicy
Key Configuration Points:
- Trust Policy Condition: Must match
system:serviceaccount:<namespace>:<service-account-name> - Audience: Must be
sts.amazonaws.comfor IRSA - OIDC Provider: Must be registered as a trusted identity provider in IAM
EKS IRSA Configuration
apiVersion: v1kind: ServiceAccountmetadata: name: s3-access-sa namespace: default annotations: eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/s3-access-roleapiVersion: v1kind: Podmetadata: name: eks-irsa-demo namespace: defaultspec: serviceAccountName: s3-access-sa containers: - name: app image: amazon/aws-cli command: ["sleep", "infinity"] # Note: The following are automatically injected by the EKS Pod Identity Webhook # when the service account has the annotation "eks.amazonaws.com/role-arn": # # Environment variables: # - AWS_ROLE_ARN: arn:aws:iam::ACCOUNT_ID:role/s3-access-role # - AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token # # Volume mounts: # - name: aws-iam-token # mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount # readOnly: true # # Volumes: # - name: aws-iam-token # projected: # sources: # - serviceAccountToken: # path: token # expirationSeconds: 86400 # audience: sts.amazonaws.com
Important: In practice, when using EKS with IRSA, you typically only need to:
- Annotate your service account with
eks.amazonaws.com/role-arn - Reference that service account in your pod spec
The pod spec would look like this:
apiVersion: v1kind: Podmetadata: name: eks-irsa-demo namespace: defaultspec: serviceAccountName: s3-access-sa containers: - name: app image: amazon/aws-cli command: ["sleep", "infinity"] # Everything else is auto-injected!
EKS will automatically inject the environment variables, volume mounts, and projected volumes for you. The full configuration above is shown to illustrate what happens behind the scenes.
How it works in EKS:

Using Projected Tokens with GKE (Google Kubernetes Engine)
GKE uses projected tokens for Workload Identity, enabling pods to authenticate as Google Cloud service accounts.
GCP-Side Configuration
Before using Workload Identity in GKE, you need to configure Google Cloud:
# 1. Enable Workload Identity on your GKE cluster (if not already enabled)gcloud container clusters update my-cluster \ --workload-pool=PROJECT_ID.svc.id.goog# 2. Create a Google Cloud service accountgcloud iam service-accounts create gcs-access-sa \ --display-name="GCS Access Service Account"# 3. Grant the GCP service account permissions to Cloud resourcesgcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:gcs-access-sa@PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/storage.objectViewer"# 4. Create the IAM policy binding between the Kubernetes SA and GCP SAgcloud iam service-accounts add-iam-policy-binding \ gcs-access-sa@PROJECT_ID.iam.gserviceaccount.com \ --role="roles/iam.workloadIdentityUser" \ --member="serviceAccount:PROJECT_ID.svc.id.goog[default/gke-workload-identity-sa]"
Key Configuration Points:
- Workload Identity Pool: Format is
PROJECT_ID.svc.id.goog - Member Binding: Must match
serviceAccount:PROJECT_ID.svc.id.goog[<namespace>/<ksa-name>] - Role: The GCP service account needs
roles/iam.workloadIdentityUserfor the K8s SA
The member format breaks down as:
PROJECT_ID.svc.id.goog– Your workload identity pool[default/gke-workload-identity-sa]–[namespace/kubernetes-service-account]
GKE Workload Identity Setup
apiVersion: v1kind: ServiceAccountmetadata: name: gke-workload-identity-sa namespace: default annotations: iam.gke.io/gcp-service-account: my-gsa@PROJECT_ID.iam.gserviceaccount.comapiVersion: v1kind: Podmetadata: name: gke-workload-identity-demo namespace: defaultspec: serviceAccountName: gke-workload-identity-sa containers: - name: app image: google/cloud-sdk:slim command: ["sleep", "infinity"] # Note: GKE Workload Identity automatically configures the GCP metadata server # in the pod. Application Default Credentials (ADC) will automatically work # without needing explicit volume mounts or environment variables.
How it works in GKE:

Note on GKE and Projected Volumes: Unlike AKS and EKS, GKE’s Workload Identity primarily works through metadata server emulation. You can optionally use projected service account tokens with a specific audience if you need direct access to the Kubernetes token, but this is rarely necessary. Most applications using Google Cloud client libraries will authenticate automatically through the metadata server without any explicit volume configuration.
Cloud Provider Comparison
Trust Relationship Overview
All three cloud providers use a similar pattern: establishing trust between the Kubernetes service account and cloud provider IAM system through OIDC federation.

Provider-Specific Comparison

| Feature | AKS | EKS | GKE |
|---|---|---|---|
| Trust Mechanism | Federated Identity Credential | IAM OIDC Provider + Trust Policy | Workload Identity Pool Binding |
| Subject Format | system:serviceaccount:ns:sa | system:serviceaccount:ns:sa | serviceAccount:PROJECT.svc.id.goog[ns/sa] |
| Audience | api://AzureADTokenExchange | sts.amazonaws.com | https://iam.googleapis.com/... |
| K8s Annotation | azure.workload.identity/client-id | eks.amazonaws.com/role-arn | iam.gke.io/gcp-service-account |
| Pod Label Required | azure.workload.identity/use: "true" | No | No |
| Auto-Injection | Yes (via webhook) | Yes (via webhook) | Yes (metadata server) |
| Env Variables Injected | AZURE_CLIENT_ID, AZURE_TENANT_ID, etc. | AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE | None (uses metadata server) |
| Volume Auto-Mount | Yes | Yes | Typically not needed |
| Cloud IAM Setup | Federated credential on App/MI | IAM Role with trust policy | IAM binding with workloadIdentityUser |
Key Benefits Across All Platforms
- No Long-Lived Credentials: Tokens expire automatically, reducing security risk
- Automatic Rotation: The kubelet handles token refresh transparently
- Fine-Grained Access: Audience scoping limits token usage
- Cloud Integration: Seamless authentication to cloud provider services
- Least Privilege: Each pod gets only the permissions it needs
Best Practices
- Set appropriate expiration times: Balance between security (shorter) and performance (fewer rotations)
- Use specific audiences: Scope tokens to their intended use
- Monitor token usage: Track authentication patterns for security insights
- Follow cloud provider guides: Each platform has specific setup requirements
- Test token rotation: Ensure your applications handle token refresh gracefully
Conclusion
Projected service account tokens represent a significant security improvement in Kubernetes authentication. Whether you’re running on AKS, EKS, or GKE, understanding how these tokens work enables you to build secure, cloud-native applications that follow the principle of least privilege without managing long-lived credentials.
The integration with cloud provider IAM systems makes projected tokens essential for modern Kubernetes workloads, providing a secure bridge between your containerized applications and cloud services.

Leave a comment