The Credential Provider Chain, End to End
Neither AWS SDK ships with your credentials baked in.
Search across all documentation pages
Neither AWS SDK ships with your credentials baked in.
Instead, each one runs a credential provider chain: an ordered list of places to look, tried one at a time until one of them produces usable credentials.
Understanding that order is the single most useful thing you can know about AWS auth, because almost every "it works on my laptop but not in production" bug is really a question of which link in the chain answered.
A provider is one strategy for obtaining credentials.
Examples include reading environment variables, parsing a file, or calling a metadata endpoint.
The chain wires these providers together in a fixed order and asks each in turn: "Do you have credentials?"
The first provider that answers wins. The rest are never consulted.
This is why order matters so much. A provider high in the chain quietly shadows every provider below it.
Both SDKs default to the same conceptual order. From highest priority to lowest:
1. Explicit credentials passed in code
2. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN)
3. Shared credentials file (~/.aws/credentials)
4. Shared config file (~/.aws/config) - profiles, SSO, assume-role, web identity, process
5. Container credentials (ECS / EKS - AWS_CONTAINER_CREDENTIALS_RELATIVE_URI)
6. Instance metadata (IMDS) (EC2 instance profile role)Steps 3 and 4 are where most of the richness lives. A profile in the shared files can itself point at an SSO session, an IAM role to assume, a web identity token, or an external credential_process. The chain resolves those sub-providers transparently.
In boto3, the chain is built by botocore's credential resolver.
When you create a Session or a client, botocore assembles the ordered list of providers and resolves the first match lazily, on the first call that needs credentials.
In AWS SDK for JavaScript v3, the equivalent is fromNodeProviderChain from @aws-sdk/credential-providers, which is what a client uses when you pass no explicit credentials.
You can also name a link in the chain directly instead of running the whole thing.
# --- Python (boto3) ---
import boto3
# Default: run the whole chain, honoring env, files, roles, metadata.
default_session = boto3.Session()
# Or pin one source: this profile from the shared config/credentials files.
scoped_session = boto3.Session(profile_name="payments-prod")
sts = scoped_session.client("sts")
print(sts.get_caller_identity()["Arn"]) # who did the chain resolve to?// --- TypeScript (AWS SDK v3) ---
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
import { fromNodeProviderChain, fromIni } from "@aws-sdk/credential-providers";
// Default: passing no credentials runs fromNodeProviderChain internally.
const defaultClient = new STSClient({ credentials: fromNodeProviderChain() });
// Or pin one source: this profile from the shared config/credentials files.
const scopedClient = new STSClient({ credentials: fromIni({ profile: "payments-prod" }) });
const who = await scopedClient.send(new GetCallerIdentityCommand({}));
console.log(who.Arn); // who did the chain resolve to?get_caller_identity / GetCallerIdentityCommand is the debugging tool for the whole chain. It returns the ARN the SDK actually authenticated as, which tells you which link answered.
A crucial property: many links return temporary credentials - a triple of access key, secret key, and session token, with an expiry. Assumed roles, SSO, web identity, container, and instance-metadata sources are all temporary. Only long-lived IAM user keys in env vars or the credentials file are permanent, and those are the ones you should be moving away from.
The SDKs refresh temporary credentials automatically. When credentials near expiry, the provider re-fetches them before the next signed request, so a long-running process does not suddenly start failing after an hour.
The chain is identical across environments by design, but the link that wins changes:
| Environment | Winning link (typical) | What supplies it |
|---|---|---|
| Local dev | Shared config file profile / SSO | aws configure or aws sso login |
| CI/CD | Environment variables or web identity | Pipeline secrets or OIDC federation |
| ECS / Fargate | Container credentials | Task role via the container metadata endpoint |
| EKS pod | Web identity (IRSA) | Projected service-account token |
| EC2 instance | Instance metadata (IMDS) | Instance profile role |
This table is the mental model for portable code. You write the SDK call once. The platform decides which credential source the chain discovers.
Two subtle interactions cause most surprises.
First, environment variables sit near the top. A leftover AWS_ACCESS_KEY_ID in a shell or a base image will beat the instance role you actually wanted, and the failure looks like a permissions problem, not a resolution problem.
Second, the default profile is implicit. If AWS_PROFILE is unset, the chain reads the default profile. On a shared machine that "default" may not be the account you think it is.
For observability, log the resolved identity at startup. A one-line get_caller_identity call at boot turns a whole class of silent misconfigurations into an obvious log entry.
An ordered list of credential sources the SDK tries one at a time. The first source that returns credentials wins, and the rest are skipped.
Explicit code credentials, then environment variables, then the shared credentials file, then the shared config file (which can resolve SSO, assumed roles, web identity, and process), then container credentials, then instance metadata.
Call sts.get_caller_identity() in boto3 or send GetCallerIdentityCommand in SDK v3. The returned ARN is the identity the chain resolved to.
Something higher in the chain answered first - usually a stray AWS_ACCESS_KEY_ID environment variable or a shared config profile. Clear those to let instance metadata win.
Rarely. You normally configure the source (a profile, a role, an env var) and let the default chain discover it. You only pin a single provider when one environment needs a specific source.
A short-lived triple of access key, secret key, and session token with an expiry. Assumed roles, SSO, web identity, container, and instance-metadata sources all return them.
No. Both SDKs re-fetch them automatically before they expire, so long-running processes keep working past the credential lifetime.
It builds the same default ordered chain a client uses when you pass no credentials. You can call it explicitly for clarity or to customize it.
The botocore credential resolver, assembled automatically when you create a Session or client. boto3.Session() runs the full chain by default.
Environment variables sit above the shared files in the chain. If both are present, the env vars win, which is why leftover exports cause confusing "wrong account" behavior.
Yes. A profile in the shared config file can declare an sso_session, a role_arn with a source, a web identity token file, or a credential_process, and the chain resolves that sub-provider transparently.
Yes - that is the point. The SDK call is identical; only the winning link differs, chosen by whatever the platform supplies.
Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Reviewed by Chris St. John·Last updated Jul 23, 2026