Photo by Dane Deaner on Unsplash
An AWS SDK v3 credentials provider is an async function that returns an access key, secret key and optional session token and expiry. Clients use the default Node.js chain unless you pass credentials. Import fromIni, fromSSO, fromTemporaryCredentials or fromNodeProviderChain from @aws-sdk/credential-providers to choose a profile, SSO or an assumed role explicitly.
This guide is for Node.js and TypeScript developers who need to control where AWS SDK for JavaScript v3 gets credentials: a named profile on a laptop, IAM Identity Center (SSO) for people, an assumed role for cross-account scripts, and the platform’s own credentials in Lambda or ECS. You’ll learn the exact order of the default chain, which aws sdk v3 credentials provider to use when, how caching and refresh work, and how to debug “Could not load credentials from any providers”.
If you came from Python, the guide to port a Python boto3 script to Node.js with AWS SDK v3 maps boto3.Session(profile_name=...) to fromIni; this page goes further into every provider and the traps between them.
How does the AWS SDK v3 find credentials by default?
When you create a client without credentials, Node.js clients use defaultProvider from @aws-sdk/credential-provider-node. Reading its source (version 3.972.84, checked September 2026), it tries these sources in order and stops at the first that returns credentials:
| # | Source | What it reads |
|---|---|---|
| 1 | Environment | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, optional AWS_SESSION_TOKEN and AWS_CREDENTIAL_EXPIRATION. Skipped when AWS_PROFILE is set. |
| 2 | SSO (inline) | Only when you pass ssoStartUrl, ssoAccountId and similar options in code; otherwise skipped. |
| 3 | Shared INI files | The selected profile in ~/.aws/credentials and ~/.aws/config: static keys, role_arn with source_profile, SSO profiles and more. |
| 4 | Credential process | A credential_process command in the profile. |
| 5 | Web identity token file | AWS_WEB_IDENTITY_TOKEN_FILE plus AWS_ROLE_ARN, as used by EKS IAM roles for service accounts. |
| 6 | Container or instance metadata | AWS_CONTAINER_CREDENTIALS_RELATIVE_URI or _FULL_URI (ECS and similar), otherwise EC2 instance metadata unless AWS_EC2_METADATA_DISABLED is set. |
The profile comes from the client’s profile option (available on clients since v3.714.0), then AWS_PROFILE, then default. Note row 1: when both AWS_PROFILE and access-key variables are set, current releases use the profile and print a warning that a future version may prefer the environment keys. Set one or the other, never both. The @aws-sdk/credential-providers README on GitHub documents every provider’s options.
Prerequisites
- Node.js 20 or later, TypeScript and
tsx. @aws-sdk/credential-providersand the clients you use; the examples use@aws-sdk/client-sts,@aws-sdk/client-s3and@aws-sdk/client-dynamodb.- The AWS CLI v2 if you use SSO: it runs the browser login that writes the token the SDK reads.
Which credentials provider should you use?
| Situation | Provider | Notes |
|---|---|---|
| Laptop, one named profile | Client profile option or fromIni({ profile }) |
Covers static keys, role_arn profiles, MFA and SSO profiles |
| IAM Identity Center profile | fromSSO({ profile }) or fromIni |
fromSSO only handles profiles that are SSO credentials themselves; a profile that assumes a role from SSO needs fromIni |
| Assume a role in code | fromTemporaryCredentials |
Calls STS AssumeRole; base credentials from masterCredentials or the default chain |
| Lambda, ECS, EC2, EKS | Nothing: the default chain | The platform sets environment variables, a container endpoint or instance metadata |
| Utilities that need credentials directly | fromNodeProviderChain() |
The default chain as a standalone function, handy for presigners and signers |
| Your own fallback order | createCredentialChain(...) |
Compose providers; .expireAfter(ms) forces periodic refresh |
Set up and test a provider, step by step
- Configure the profileRun
aws configure ssofor Identity Center, or add a[profile name]section withrole_arnandsource_profilefor an assume-role profile. - Log in if it’s SSORun
aws sso login --profile name. The SDK reads the cached token; it doesn’t open a browser itself. - Pick the provider in codePass it as
credentialson the client, or set the client’sprofileoption and let the chain do the rest. - Call STS GetCallerIdentityIt needs no IAM permission and tells you exactly which account and role the SDK is signing as.
- Check the expiryTemporary credentials carry an
expiration; long-term keys don’t. That tells you whether refresh will happen.
Example: fromIni, fromSSO and assume role in one script
// credentials.ts
// Pick a credential provider explicitly and print who the SDK is signing as.
// Usage: npx tsx credentials.ts [ini|sso|assume|chain] [profile-name]
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
import {
fromIni,
fromSSO,
fromTemporaryCredentials,
fromNodeProviderChain,
} from "@aws-sdk/credential-providers";
import { createInterface } from "node:readline/promises";
const profileName: string | undefined = process.argv[3];
async function askMfaCode(serial: string): Promise<string> {
const rl = createInterface({ input: process.stdin, output: process.stdout });
try {
return (await rl.question(`MFA code for ${serial}: `)).trim();
} finally {
rl.close();
}
}
function pickProvider(mode: string) {
switch (mode) {
case "ini":
// A named profile from ~/.aws/credentials or ~/.aws/config, including
// role_arn + source_profile profiles. Prompts for MFA if the profile has mfa_serial.
return fromIni({ profile: profileName ?? "dev", mfaCodeProvider: askMfaCode });
case "sso":
// A profile configured with `aws configure sso`. Run `aws sso login` first.
return fromSSO({ profile: profileName ?? "dev-sso" });
case "assume":
// Assume a role in another account, starting from whatever the default chain finds.
return fromTemporaryCredentials({
params: {
RoleArn: process.env.TARGET_ROLE_ARN ?? "arn:aws:iam::210987654321:role/ReadOnlyAudit",
RoleSessionName: "audit-script",
DurationSeconds: 3600,
},
clientConfig: { region: "us-east-1" },
});
default:
// The same chain clients use when you pass no credentials at all.
return fromNodeProviderChain();
}
}
const mode = process.argv[2] ?? "chain";
const sts = new STSClient({ region: process.env.AWS_REGION ?? "us-east-1", credentials: pickProvider(mode) });
const who = await sts.send(new GetCallerIdentityCommand({}));
const creds = await sts.config.credentials();
console.log({
mode,
account: who.Account,
arn: who.Arn,
accessKeyId: `${creds.accessKeyId.slice(0, 4)}...`,
expires: creds.expiration?.toISOString() ?? "never (long-term keys)",
});
npm install @aws-sdk/client-sts @aws-sdk/credential-providers
npm install --save-dev tsx typescript
aws sso login --profile dev-sso
npx tsx credentials.ts sso dev-sso
# {
# mode: 'sso',
# account: '123456789012',
# arn: 'arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_ReadOnly_0123456789abcdef/jane',
# accessKeyId: 'ASIA...',
# expires: '2027-01-22T17:04:11.000Z'
# }
TARGET_ROLE_ARN=arn:aws:iam::210987654321:role/ReadOnlyAudit npx tsx credentials.ts assume
Account IDs and names are placeholders. fromIni with mfaCodeProvider prompts for a code when the profile has mfa_serial; without the callback it fails with “Profile dev requires multi-factor authentication, but no MFA code callback was provided.”
Assuming a role in several accounts
// multi-account.ts
// One provider per account, each assuming a role from the same base credentials.
// fromNodeProviderChain memoizes, so the base credentials are resolved once and shared;
// each S3Client caches its own assumed-role credentials and refreshes them before expiry.
import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";
import { fromTemporaryCredentials, fromNodeProviderChain } from "@aws-sdk/credential-providers";
const base = fromNodeProviderChain();
const accounts = ["111122223333", "444455556666"];
for (const account of accounts) {
const credentials = fromTemporaryCredentials({
masterCredentials: base,
params: { RoleArn: `arn:aws:iam::${account}:role/ReadOnlyAudit`, RoleSessionName: "bucket-inventory" },
clientConfig: { region: "us-east-1" },
});
const s3 = new S3Client({ region: "us-east-1", credentials });
const { Buckets } = await s3.send(new ListBucketsCommand({}));
console.log(`${account}: ${Buckets?.length ?? 0} buckets`);
}
When you omit RoleSessionName, the provider generates aws-sdk-js- plus a timestamp. Set a meaningful one: it appears in the assumed-role ARN and in CloudTrail in the target account. DurationSeconds defaults to 3,600 and can go from 900 up to the role’s maximum session duration (at most 12 hours), but a role assumed from another role’s session (role chaining) is limited to one hour.
A custom chain with forced refresh
// custom-chain.ts
// Try environment variables first, then a named profile; re-resolve at least every 15 minutes.
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { createCredentialChain, fromEnv, fromIni } from "@aws-sdk/credential-providers";
export const ddb = new DynamoDBClient({
region: "us-east-1",
credentials: createCredentialChain(fromEnv(), fromIni({ profile: "ci" })).expireAfter(15 * 60_000),
});
How are credentials cached and refreshed?
Each client caches what its provider returns. If the credentials have an expiration, the client calls the provider again once less than 5 minutes remain; if they don’t, it calls the provider only once. Two consequences follow:
- Create clients once and reuse them. A new client per request means a new cache, and with
fromTemporaryCredentialsa new STS call each time. - Caches aren’t shared between clients. The SDK README says so directly.
fromNodeProviderChainmemoizes internally, so reusing one instance asmasterCredentialsresolves the base credentials once.
With createCredentialChain(...).expireAfter(15 * 60_000), the provider is called roughly every 10 minutes under continuous use, which is useful when rotated long-term keys arrive through environment variables or a file. For long-running jobs also look at timeouts: a stalled STS call blocks every request waiting for credentials, as covered in the guide to AWS SDK v3 retry and timeout configuration.
What about Lambda, ECS and other AWS compute?
Don’t pass credentials at all. Lambda sets AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN from the execution role, so step 1 of the chain picks them up. ECS tasks get a container credentials endpoint (step 6), EKS service accounts a token file (step 5), and EC2 instances the instance metadata service (the script to find EC2 instances without IMDSv2 checks that it only answers token-based requests). Hard-coding fromIni in code that also runs on AWS is a classic bug: it works on your laptop and fails in Lambda with “Could not resolve credentials using profile”. If you need a profile locally, set AWS_PROFILE in your shell instead.
Secrets your code reads at runtime are a separate concern from these credentials; the guide to get a Secrets Manager secret value with AWS SDK v3 covers that side, and the guide to get SSM Parameter Store values with AWS SDK v3 covers plain configuration.
Which permissions does each provider need?
fromIniwith static keys,fromEnv: none beyond what your calls need.fromSSO: none in IAM. Access comes from the permission set assigned to you in IAM Identity Center.fromTemporaryCredentialsandrole_arnprofiles: the base identity needssts:AssumeRoleon the target role’s ARN, and the target role’s trust policy must allow that identity. Both sides are required. Roles created for one-off cross-account scripts tend to linger; the script to find unused IAM roles with RoleLastUsed flags them.- GetCallerIdentity: no permission needed, which makes it the right first call.
To see what the assumed role can actually do, run the example to check the permissions of your currently assumed IAM role, and see how to find the IAM actions your AWS SDK for JavaScript code needs before writing the role’s policy.
Troubleshooting credential errors
Could not load credentials from any providers. Every link in the chain came up empty. CheckAWS_PROFILEspelling, that the profile exists in the files, and on EC2 that an instance profile is attached.The SSO session associated with this profile has expired. Runaws sso loginagain. Long-running processes need a new login when the SSO session ends.AccessDeniedonsts:AssumeRole. Either the caller lackssts:AssumeRoleor the trust policy doesn’t name the caller. The guide to troubleshoot AWS IAM access denied errors step by step covers both sides.- Calls go to the wrong account. Stray
AWS_ACCESS_KEY_IDvariables from an old shell win over~/.awswhenAWS_PROFILEisn’t set. PrintGetCallerIdentityat start-up in scripts that can do damage. - STS calls fail with a Region error. The inner STS client takes its Region from
clientConfig, then the profile, then the outer client, thenAWS_REGION. SetclientConfig: { region }explicitly for assume-role providers.
Limits of credential providers
Most providers need Node.js: fromIni, fromSSO, fromNodeProviderChain and fromProcess read local files or processes and don’t work in browsers. None of them performs the SSO browser login for you. And a provider only decides who you are; it can’t grant more than the identity’s policies allow. Browser and mobile apps usually get AWS credentials through Amazon Cognito instead; to manage that user pool from a backend, see how to create Cognito users from code with AWS SDK v3.
ChatWithCloud reads the same ~/.aws profiles, including SSO and assume-role-with-MFA profiles, with one profile and Region per session; the guide to connect ChatWithCloud to your AWS account with profiles, SSO and roles shows that setup. Its generated code uses SDK v2 rather than v3, and your credentials stay in ~/.aws, as the ChatWithCloud security page explains.
Frequently asked questions
How do I use a named profile with AWS SDK v3?
Set profile: "name" on the client, pass credentials: fromIni({ profile: "name" }), or run with AWS_PROFILE=name.
What is the difference between fromSSO and fromIni for SSO profiles?
fromSSO handles only profiles that are SSO credentials. If the profile assumes another role on top of SSO, use fromIni, which understands both.
Do I need to refresh assumed-role credentials myself?
No. The client re-calls the provider when less than 5 minutes remain before expiry, as long as you reuse the client.
Why does the SDK ignore my AWS_ACCESS_KEY_ID?
AWS_PROFILE is also set. The default chain skips environment keys when a profile is selected.
Related guides
Ask your AWS account in plain English
Your first 15 runs are free, with no OpenAI key needed.
npx chatwithcloud