AWS SSO "Token Has Expired and Refresh Failed": The Fix

Logeshwaran
—

"Error when retrieving token from sso: Token has expired and refresh failed" means the AWS CLI tried to renew your IAM Identity Center sign-in behind the scenes and could not, so it never got as far as asking for AWS credentials. The quick fix is aws sso login --profile my-dev, then aws sts get-caller-identity --profile my-dev to prove you are back. If the error keeps returning, your profile is almost certainly in the old four-line layout that cannot refresh at all, and moving it to an sso-session block fixes it for good. The trap that catches careful people: asking your admin for a longer permission-set session does not lengthen your sign-in. There are two clocks, and the one everyone extends is not the one that logged you out.

Jake's phone repair shop runs a small appointment system on AWS: a few S3 uploads, a scheduled report, nothing dramatic. One morning he opened his terminal before the shop opened, ran the script he runs every week, and got a line with the word "token" in it. His first thought was that the account had been hacked. His second was to delete the whole .aws folder, because a forum post said so. Ethan, who looks after AWS for a handful of local businesses, talked him out of both. "Nothing is hacked, and nothing needs deleting. One sign-in expired, and we are going to find out which one before we touch anything." This page is that conversation, in the order Ethan actually works: the five-minute checks that find the failing layer, the one-time fix that stops the error coming back, and the handful of cases where the terminal is fine and it is Terraform, VS Code or a Python script that is holding a dead credential.

⚡ Quick answer

• Right now: aws sso login --profile my-dev, then aws sts get-caller-identity --profile my-dev. Check the account and ARN it returns.

• Keeps coming back: your profile uses the legacy layout with four sso_ lines under [profile]. Move to an [sso-session] block; it refreshes, the old one never does.

• CLI works, app does not: Terraform, VS Code or your script is holding its own stale credentials. Restart it and check which profile it really uses.

• No browser on this machine: aws sso login --profile my-dev --use-device-code and finish on your phone or laptop.

• Leave the cache alone unless the steps below point at it. aws sso logout is the clean way, deleting the folder is the last resort.

What actually expired: the three clocks behind one error

If you only read one section, read this one, because the error names three different things that can expire, and you are about to find out which one is yours. When you sign in with IAM Identity Center, you are not getting an access key. You are getting a short-lived access token for your sign-in, plus a refresh token that can quietly fetch a new access token while your sign-in session is still alive. The CLI then trades that access token for temporary AWS credentials for one account and one permission set, and those credentials have their own lifetime too.

So there are three clocks. Your sign-in session, which your company's admin sets, 8 hours by default. The access token, which is hourly and refreshes itself as long as the sign-in session is alive. And the account credentials for the role you are using, 1 hour by default, up to 12. The error on this page is the middle clock failing to refresh, and it fails for exactly two reasons: either the sign-in session itself has ended, so there is nothing left to refresh from, or your profile is in the layout that has no refresh token at all.

That is the whole mystery, and it is why the error can look random. Jake signs in at 9, works all morning, and at 5:10 the refresh fails because his 8-hour session ended. His colleague with the old-style profile sees it after 8 hours too, but for a different reason: her profile never had a refresh token, so AWS's own documentation describes it as a fixed eight-hour session that cannot be renewed. Same sentence on the screen, two different fixes.

The message has siblings. error when retrieving token from sso, error loading sso token, aws sso token expired, and aws sso login not working are all the same family, and the wording alone will not tell you which clock stopped. Reproduce it with the very profile the failing command used, and the answer separates itself in a few minutes.

Jake: "Three clocks? I only pressed one button."

Ethan: "You did, and the button wound all three. We just need to find which one ran down. Most mornings it is the first, and the fix is pressing the button again."

The five-minute triage that finds the failing layer

Before any of this, breathe. Nothing in the next five minutes deletes anything. You are going to run six commands, all read-only except the sign-in itself, and by the end you will know whether this is an ordinary expiry, a profile that cannot refresh, or an application holding something stale.

  1. aws --version. Write down the exact output. Whether it is v1 or v2, and which v2, matters later.
  2. aws configure list-profiles. Find the profile your failing command actually used. If you did not pass --profile, it used default or whatever AWS_PROFILE points at.
  3. aws configure list --profile my-dev. This shows where each setting comes from, which is how you catch an environment variable overriding your file.
  4. aws sts get-caller-identity --profile my-dev. Note what happens: the SSO refresh error, a different error, or success.
  5. aws sso login --profile my-dev. Finish the sign-in in the browser.
  6. Run step 4 again. If it works, read the account ID and ARN it returns. The right profile returning the wrong account is its own problem, and you want to catch it here rather than after a command runs.

On a shared workstation, pass --profile every time while you investigate. A named profile is like choosing the right drawer in a filing cabinet; a perfectly good key found in the wrong drawer still opens the wrong account. And check your environment without pasting it anywhere public: env | grep ^AWS_ on macOS and Linux, Get-ChildItem Env:AWS* in PowerShell. A leftover AWS_SESSION_TOKEN from last week explains more "it works for you but not for me" afternoons than any other single thing. Record the variable names, never the values; those are secrets.

Now match what you saw against the table.

What you sawWhat it usually meansWhat not to do
Login fixed the CLI and the appOrdinary session expiryDelete profiles
Login fixed the CLI, app still failsThe app holds its own credentials or an environment overrideChange permission sets
The login page never completesBrowser, proxy, identity provider, or the wrong sign-in flow for a remote machineKeep deleting the cache
Identity returns the wrong accountWrong profile or account assignmentAssume SSO is broken
Login works, command says AccessDeniedPermissions on the role you choseLengthen session durations
Login works, service says ExpiredTokenStatic temporary credentials in the running processClear browser cookies first

Why it keeps coming back

Jake: "It worked all week and then this morning, nothing. I did not change anything."

Ethan: "You probably did not. Sessions have a lifetime, and the old profile format cannot renew itself. Once you see which pattern you are in, the fix is one of three things."

The first pattern is the session reaching its lifetime, or an admin ending it. Refresh only works while your sign-in session is alive, and a company can shorten that window, or revoke sessions, whenever it likes. A new browser sign-in starts a fresh session; nothing you do locally can stretch the old one. If you can sign in at the portal but your account is not listed, that is an assignment problem, not a token problem, and no amount of logging in fixes it.

The second pattern is the legacy profile. If your config has sso_start_url, sso_region, sso_account_id and sso_role_name sitting directly under [profile my-dev], you have the layout that AWS now calls legacy and non-refreshable. It can sign in. It cannot refresh. After eight hours it is simply over, and any script that depends on it will ask you to sign in again every single day. The fix is a one-time edit, and it is the next section.

The third pattern is the tool, not the profile. A second AWS CLI on your PATH, an old Python environment, a container with a dated SDK, or an editor extension bundled with its own dependencies can all keep using an older way of reading your profile, and an older reader cannot refresh the newer layout. The cure is to update the binary the failing process actually runs, not every AWS tool you can find.

Then there are the environment traps: Windows and WSL with different home folders, a terminal opened before you changed a variable, a proxy that blocks the sign-in endpoint, a clock that is wrong enough to make expiry timestamps lie. Turning off TLS verification as a workaround trades away a real protection and still leaves the bad profile in place, so it is never the answer here; fix the clock and the proxy instead.

Jake: "If I can see my S3 bucket in the browser, why does the terminal not know I am signed in?"

Ethan: "Because the browser and the terminal each hold their own sign-in. Same identity, two separate sessions. One being alive says nothing about the other."

The fix you make once: move to an sso-session block

This is the fix you make once and stop thinking about. If your config file still looks like the first block below, you have been signing in more often than you needed to for a long time.

# the legacy, non-refreshable layout
[profile my-dev]
sso_start_url = https://example.awsapps.com/start
sso_region = us-east-1
sso_account_id = 111122223333
sso_role_name = DeveloperAccess
region = us-east-1
output = json

The refreshable layout separates how you sign in from which account and role you want. The session block carries the portal and the Identity Center Region. The profile points at the session by name and adds the account and role. The name after sso_session = must match the bracketed block exactly.

# the refreshable layout
[profile my-dev]
sso_session = company-sso
sso_account_id = 111122223333
sso_role_name = DeveloperAccess
region = us-east-1
output = json

[sso-session company-sso]
sso_start_url = https://example.awsapps.com/start
sso_region = us-east-1
sso_registration_scopes = sso:account:access

Replace the portal, the account ID and the role name with yours. If you are wondering what your AWS SSO start URL is, it is on the IAM Identity Center dashboard as the AWS access portal URL, and from CLI version 2.22.0 the issuer URL on the same page works too. The sso_region is where your company's Identity Center lives, which is not necessarily where you run your S3 or EC2 commands; the plain region in the profile is the default for those. The scope sso:account:access is the minimum that gets you a refresh token back, and there is no reason to add scopes beyond it. The account ID and role name have to be yours, too; a value pasted from somebody else's example looks right and fails, because those assignments belong to their workforce identity, not yours.

You do not have to type any of this. The wizard writes it for you.

aws configure sso --profile my-dev
aws sso login --profile my-dev
aws sts get-caller-identity --profile my-dev

The wizard asks for a session name, the start URL, the Identity Center Region and the scope, opens the browser, then lets you pick the account and role from the ones you are assigned. If you would rather do it by hand, or teammates rely on the exact profile names, the safe order is short.

  1. Copy ~/.aws/config somewhere safe. You are about to edit one entry, and a backup makes the edit fearless.
  2. Find the legacy profile the failing command used, and note its account ID, role name, portal URL and Identity Center Region.
  3. Rewrite that one profile in the two-block layout above, or run aws configure sso --profile my-dev and let the wizard write it.
  4. Sign in with aws sso login --profile my-dev and confirm the account with aws sts get-caller-identity --profile my-dev.
  5. Run one harmless read-only command you use every day. If the identity is right but the action is denied, that is a permissions question, not a sign-in one.
  6. Restart any editor, script or Terraform shell that was failing, so it picks up the new layout instead of its old cached state.

Edit only the profile that was broken and leave the rest of the file alone. One session block can serve several profiles: dev and prod can both point at company-sso, and one aws sso login covers all of them. It is not a tool for two different people sharing one laptop, though. Give each person their own session name and never share cached sign-ins.

Jake: "So this is the thing I should have done a year ago."

Ethan: "This is the thing most people should have done a year ago. It takes two minutes, and then the error only comes back when your session genuinely ends, which is once a day at most and usually less."

What lives in ~/.aws/sso/cache, and when it is safe to clear

This is the folder every forum answer tells you to delete. You can, but you rarely need to, and knowing what is in it makes the decision calm instead of desperate. It holds the CLI's cached sign-in tokens as JSON files with hashed names. With the refreshable layout, the file name comes from the session name; with the legacy layout, from the start URL. The files are sensitive even though they look like gibberish. Keep their JSON out of support tickets and log archives.

Your config file is not in there. Your credentials file is not in there either. The CLI also keeps ~/.aws/cli/cache for other temporary credentials. These are different drawers, and a stale sign-in in one is no reason to empty the others.

LocationWhat it holdsWhen you touch it
~/.aws/configNamed profiles, session blocks, RegionsBack up, then edit only the broken entry
~/.aws/credentialsStatic keys, possibly secretsNever, for an SSO refresh error
~/.aws/sso/cacheCached Identity Center sign-in tokensaws sso logout first; manual clearing only for stubborn state
~/.aws/cli/cacheCached temporary credentials for assumed rolesOnly if stale role credentials are the suspect

The clean order is: close the long-running tool that was failing, sign out, sign in, prove it.

aws sso logout
aws sso login --profile my-dev
aws sts get-caller-identity --profile my-dev

One thing to know before you run that: aws sso logout signs you out of all SSO profiles on the machine, so other profiles will want a fresh login too. It is still far gentler than deleting the folder. If the cache really does look corrupted after that, move its contents to a private backup folder rather than deleting them, sign in again, and remove the backup securely once you are working. Editing the expiry timestamps inside the token JSON is a tempting shortcut, and it does nothing: the local file looks newer, but Identity Center is the one that decides validity, and it has not changed its mind. Copying a colleague's working cache is the same mistake with a security problem attached.

Windows, PowerShell, WSL, macOS and Linux: which file did you actually edit?

If you are on Windows, or you move between Windows and WSL, give this section a minute. Half of the "my edit did nothing" stories end here, with the right change made to the wrong file. Windows keeps your AWS files under %USERPROFILE%\.aws. WSL keeps its own under the Linux home folder. A dev container has another. An SSH session to a server has another still. Editing one while the failing command reads another is indistinguishable from an edit that did nothing.

REM Command Prompt
echo %USERPROFILE%
dir "%USERPROFILE%\.aws\sso\cache"

# PowerShell
$env:USERPROFILE
Get-ChildItem "$env:USERPROFILE\.aws\sso\cache"
notepad "$env:USERPROFILE\.aws\config"

# Linux, macOS, WSL
echo "$HOME"
ls -la "$HOME/.aws/sso/cache"
command -v aws

Command Prompt wants the literal %USERPROFILE%; PowerShell wants $env:USERPROFILE. In WSL, ~ is the Linux home, not the Windows profile. A scheduled task or a service running as a different Windows account has a different profile folder and therefore a different cache, which is why a script that works when you run it by hand can fail at 3 a.m. under another account.

Two environment variables move the files themselves: AWS_CONFIG_FILE and AWS_SHARED_CREDENTIALS_FILE. If your edits seem invisible, check those first. And remember that a running program inherited its environment when it started. Changing a variable in a new window does not reach the old window, the IDE, or the service that was already running. Restart the thing that is failing, then test again.

CLI v1 versus v2, PKCE, and the second aws on your PATH

You may have read that AWS CLI v1 cannot use Identity Center at all. That is not quite it. The distinction that matters is SSO session management. What counts is whether the installed binary understands the sso-session layout. A current AWS CLI v2 does. The legacy four-line profile works on either major version, but it is fixed at an eight-hour session with no refresh at all. For day-to-day interactive work, use a current v2, especially if you need the newer sign-in options.

One of those options changed quietly. From version 2.22.0, the CLI signs in with PKCE by default, a browser-based flow that must be completed on the same device that started it. Older v2 builds used the device-authorization flow, where you get a code to type on any device. Both still exist; v2.22.0 and later just pick PKCE unless you ask otherwise. That is why a command copied from a current tutorial can behave differently on an older install on the same desk.

aws --version
where.exe aws          # Windows
command -v aws         # macOS and Linux
aws sso login help     # which sign-in flags this build supports

Windows can end up with more than one aws on the PATH after a couple of installers. macOS can have a package-manager copy and a direct install. A container does not see the host's upgrade at all. Before you change a profile again, confirm which binary the failing process runs and what version it is. Your SDKs are a separate question: a current CLI can refresh its own token while an older SDK inside an application cannot read the refreshable layout. Update the one that owns the failing process rather than loosening your company's session rules to suit old software.

Signing in on a server with no browser

If your terminal is an SSH session to a server with no desktop, the browser will never open, and that is fine. You have two switches, and picking the right one depends on where you can see a browser. --no-browser just stops the CLI trying to open one. --use-device-code switches to the device-authorization flow, which prints a URL and a short code you can finish on any device with a browser, your phone included.

aws sso login --profile my-dev --no-browser --use-device-code

Here is the remote-machine puzzle that the PKCE change created. On 2.22.0 and later, the default sign-in prints a long authorization link that must be opened on the same machine that started the login. People copy it to their laptop, finish the sign-in there, and nothing happens on the server. If the browser is on a different device, you want the device-code flow, not a second try with the same link. That live device code is a key to your account for the next few minutes, so it stays with you; nobody else, and no chat assistant, should ever be asked to finish the login.

⚠ The remote job trap

A production cron task that asks for device authorization every time a token expires still needs a human at the keyboard, and it stalls the moment nobody is there. Both of these flows are for people. Unattended work needs a workload identity, which is the last section of this page.

Two clocks: your sign-in session and your permission set

Jake: "Can I just ask the admin for a longer session?"

Ethan: "You can ask, but ask for the right one. There are two clocks here, and the one people usually lengthen is not the one that logs you out."

Identity Center keeps the user interactive session, which is how long you can stay signed in before you must authenticate again, separate from the permission set session, which is how long each set of AWS account credentials lasts. Lengthening one does nothing to the other.

ClockDefaultRangeWhere it is set
User interactive session (your sign-in)8 hours15 minutes to 90 daysIdentity Center → Settings → Authentication → Session duration
Permission set session (account credentials)1 hour1 to 12 hoursIdentity Center → Permission sets → General settings
SDK or process credential cacheDepends on the toolNot an admin settingThe application's own provider

Walk through a day with the defaults. You sign in at 9:00 with an 8-hour session and a 1-hour permission set. Your account credentials expire around 10:00, and with the refreshable layout the CLI fetches new ones without bothering you, over and over, until 17:00, when the session itself ends and the next refresh fails with the error on this page. Now suppose you ask for a 12-hour permission set to stop the error. Your credentials last longer, and your session still ends at 17:00. You gained nothing. If the admin instead sets the interactive session to 12 hours, you gain four hours, and only for sessions started after the change; sessions already running keep their old length.

There is a smaller wrinkle worth knowing. Credentials you already hold can stay valid for a while after the session stops allowing new ones, so the last command that worked and the first refresh that failed need not be the same minute. Read the error, not the clock on the wall.

Changing either duration is an admin job, done in the console or with aws sso-admin update-permission-set --session-duration PT4H and the two ARNs. A changed permission set has to be re-provisioned to the accounts that use it. None of this is the first fix for a developer whose profile cannot refresh; it is a policy decision about how long people should stay signed in.

Python, Node and long-running processes: what did they pick?

Your CLI being happy does not mean your Python script or your Node service is. They choose credentials in their own order, and this is where you find out what yours chose. Every AWS SDK walks a provider chain: credentials you passed in code, then environment variables, then the shared config, then the rest. The question is never "did I run aws sso login" on its own. It is "which provider did this process pick when it started".

AWS's SDK reference lists sso-session support for the current CLI v2, Boto3, the SDK for JavaScript in both 2.x and 3.x, Java 2.x, Go, .NET, PHP, Ruby, Kotlin, Swift, C++, and the Tools for PowerShell. Java 1.x has no support at all, and Rust supports only the legacy layout. Use a current release and check the environment the application really runs in, not the one on your laptop.

# Python
import boto3
session = boto3.Session(profile_name="my-dev")
identity = session.client("sts").get_caller_identity()
print(identity["Account"], identity["Arn"])
// Node.js
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
import { fromIni } from "@aws-sdk/credential-providers";
const sts = new STSClient({ region: "us-east-1", credentials: fromIni({ profile: "my-dev" }) });
const r = await sts.send(new GetCallerIdentityCommand({}));
console.log(r.Account, r.Arn);

Run aws sso login --profile my-dev first. If the script still fails while the CLI's identity call succeeds, look at the SDK version in that environment, whether it names the profile or was handed a static credential object, and whether it was started before you signed in. A long-running worker that was launched yesterday is still holding yesterday's provider state; restart it before you conclude the profile is broken. A session token copied into source code or a notebook cell is a time bomb with a one-hour fuse.

And if the application runs on Lambda, ECS or EC2, it should not be using your sign-in at all. Those have execution roles and instance roles. Your laptop's human session belongs on your laptop.

The one that fools people: if you set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN for a process, those exact strings never change when you sign in again in another window. The process keeps using an expired STS session and reports ExpiredToken, which is a different error with a different fix. Remove the stale variables, restart the process, check the identity.

Terraform: the CLI works and plan still fails

Jake: "The terminal works now. Terraform still says the token expired. How is that possible?"

Ethan: "Because Terraform is not using the thing you just fixed. It has its own idea of where credentials come from, and sometimes two ideas. Let's find which one it picked."

Terraform has at least two credential consumers: the AWS provider that creates your resources, and the backend that stores your state in S3. They need not use the same profile. Backend initialization runs before the provider is evaluated, so terraform init can fail on the backend while your provider block is perfectly fine. Notice which stage fails, init, plan, a state read, or one resource, because each points at a different consumer.

aws sso login --profile my-dev
aws sts get-caller-identity --profile my-dev
export AWS_PROFILE=my-dev        # PowerShell: $env:AWS_PROFILE = "my-dev"
terraform plan

Check the Terraform version and the locked AWS provider version too. An old provider may not read the refreshable layout. Update it deliberately and review the lock file change rather than deleting the lock file. Refresh the human sign-in first, then run a plan, which changes nothing, before you run anything that does.

Dockerized Terraform is its own small trap. The container may mount your config but not your token cache, or run as a different user with a different home folder, so the host terminal works and the container does not. Decide whether a human sign-in belongs in that container at all. For local development, mount what it needs on purpose. For CI, use a role or federation on the runner; a developer's cached sign-in copied into a pipeline is the wrong identity for the job and expires at the worst moment.

VS Code and the AWS Toolkit: which one is signed in?

VS Code can run the CLI in its integrated terminal while the AWS Toolkit extension holds a separate connection of its own. Add Remote SSH, WSL, or a dev container and the editor's tools may be reading a different file system and a different home folder. So "it works in the terminal" and "the extension is signed in" are two different statements, and they can disagree without anything being broken.

Check which connection the Toolkit has selected and whether it matches the profile you intend. Reconnect it through the extension's own sign-in if it is stale, update the extension if it predates the refreshable layout, and restart the window. A stale label in the sidebar is not a reason to delete your AWS configuration; reconnecting the extension is enough. If a debug task fails while the integrated terminal works, look at the launch configuration's environment; it can pass a profile or a token that differs from your shell, and the process's actual environment decides, not the editor's title bar.

Jake: "Why does the little terminal at the bottom work but the button in the sidebar does not?"

Ethan: "They sign in separately. We only need to fix the one that is stale, not every AWS login on your machine."

ExpiredToken, AccessDenied and "Unable to locate credentials" are not this error

You will meet three errors that all sound like "something expired". They are not the same problem, and treating them the same is how an afternoon disappears. Here they are side by side.

What you seeWhich layer failedBest next move
Token has expired and refresh failedGetting an Identity Center tokenSign in; fix the profile layout if it recurs
ExpiredTokenTemporary AWS credentials used to sign a requestFind where the process got its credentials and give it refreshable ones
AccessDeniedPermissions on the role you are usingCheck the account, ARN and permission set for that action
Unable to locate credentialsCredential discovery found nothingProfile name, environment, runtime identity
Sign-in works, account not listedAccount assignmentAsk the Identity Center admin

Picture the script that saved temporary credentials in a .env file last Tuesday. You sign in again today; the file does not change; the script fails with ExpiredToken. A longer portal session would not help it, because it is not using the portal. The reverse is also true: if the CLI cannot get an SSO token at all, no bucket policy on earth is the fix. And a successful get-caller-identity proves who you are, not what you may do; a valid session can still be denied a new action.

Wrong account, wrong Region, wrong profile

A sign-in can be perfectly healthy and still land your command in the wrong account. This is the dev, staging and production problem: one person, three roles, three profiles, and one habit of forgetting --profile. Write the mapping down in your config, read the account ID that get-caller-identity returns, and pass the profile explicitly on anything you would not want to run in production by accident. An unexpected identity is a stop sign.

Two Regions, two meanings. sso_region is where your company's Identity Center lives. The profile's region is where your commands go by default. They are allowed to differ, and making them match "to be safe" is how people point the sign-in at a Region that has no Identity Center in it. An empty list from aws ec2 describe-instances in another Region is not a token problem, however much it feels like one; keep "who am I" and "where are my things" as two separate checks.

If your company signs people in through an external identity provider, that provider's own session rules and user status sit upstream of everything here. You can be able to sign in and still get no accounts, because an administrator changed your assignments. No local cleanup restores an assignment that is no longer there, and no account number typed into a config file grants access that was never assigned.

When sign-in itself fails: what to look at and what to send

If a fresh, correctly laid-out profile still cannot complete sign-in, stop guessing and watch the stage where it stops. Does the CLI print a browser URL at all? Does the browser show an identity-provider error? Does the device-code flow complete but the CLI never receive anything? Does the sign-in finish and the first STS call fail? Each of those is a different owner.

aws --version
aws configure list --profile my-dev
aws sso login --profile my-dev --debug
aws sts get-caller-identity --profile my-dev --debug

--debug prints everything, including token-related data and account details, so redact before you share it. For a helpdesk or an AWS Support case, send the CLI version, the operating system and where the command ran, the UTC time, the Identity Center Region, whether PKCE or the device flow was used, the sanitized error, and the result of the explicit-profile identity call. Say whether the failure reproduces in the CLI alone or only in an SDK, an editor or Terraform.

Then route it to whoever owns the failing layer. A proxy blocking the sign-in endpoint is the network team. An identity provider rejecting the login is the identity team. A missing assignment is your Identity Center admin. Support cannot turn a disabled user back on, and nobody can make an ended session valid by editing a timestamp. If it worked yesterday and not today, ask what changed: the CLI on the PATH, an SDK dependency, an assignment, a proxy, a policy, the clock, a permission set, a session duration. The answer is in that list more often than not, and creating a permanent access key to escape a human sign-in inconvenience is the one move that makes everything worse.

Making the next expiry a non-event

You are back in. This last part is about making the next expiry a non-event instead of a surprise, without pretending a sign-in can be made permanent. Refreshable is not immortal. The sso-session layout renews quietly while your session lives, and when the session ends you sign in again. That is a feature of workforce security, not a bug to engineer around.

For your own machine: keep a named profile, run the identity check before anything risky, and never export role credentials into your shell history or a secrets file. For onboarding a teammate, hand them a config snippet with placeholders, not tokens, and explain the two Regions so they do not aim the sign-in at the wrong one. For a long-running local process, use a real SDK credential provider and restart it after you sign in. And if a program must run for days with nobody present, it is a workload, not a person: give it an IAM role, an execution role, or an approved federation, and stop trying to keep a browser login alive.

For the people who set policy: choose the interactive and permission-set durations from how people actually work and from your threat model, remember that a change applies only to new sessions, and tell staff the tradeoff plainly. A scheduled script that runs aws sso login on its own, or scrapes device codes, defeats the point of the login; sign-in has to stay something a person does on purpose.

Jake can now tell in a minute whether his shop's script failed because he needs to sign in, because an old profile cannot refresh, or because a process should never have been using his sign-in at all. Ethan's rule for him fits on a sticky note: keep the person's login for the person, and give the application an identity of its own.

The questions people type into a search box at 9 a.m.

How do I fix "error when retrieving token from sso" in the AWS CLI?

Run aws sso login --profile my-dev, then aws sts get-caller-identity --profile my-dev and check the account it returns. If the error comes back within a day, your profile is in the legacy layout; move it to an sso-session block so it can refresh.

What does "Token has expired and refresh failed" mean?

The CLI could not renew the Identity Center token it uses to fetch account credentials. Either your sign-in session has ended, or your profile layout has no refresh token. A new sign-in fixes the first; the sso-session layout fixes the second.

What is an SSO token in AWS?

It is the short-lived token you receive when you sign in through IAM Identity Center. The CLI and SDKs exchange it for temporary credentials for one account and permission set. It is not an access key, and it is cached under ~/.aws/sso/cache.

Why do I get "error loading sso token"?

The tool cannot find or read a usable cached sign-in for that profile. Check the profile name, sign in with that exact profile, and make sure the tool is reading the same home folder and config file you edited.

Why is aws sso login not working after I sign in?

Usually the following command used a different profile, a different home folder, or a process started before the sign-in. Pass --profile explicitly, check AWS_PROFILE and the AWS_ACCESS_KEY_ID family of variables, and restart the application that was failing.

Where is ~/.aws/sso/cache on Windows?

Under %USERPROFILE%\.aws\sso\cache in Command Prompt, or $env:USERPROFILE\.aws\sso\cache in PowerShell. WSL, dev containers and scheduled tasks running as another user each have their own.

Should I delete every file in the AWS SSO cache?

Not as a first move. Run aws sso logout and sign in again, which signs out every SSO profile on the machine but leaves your config alone. Clear the folder only if the cache is still inconsistent after that, and never share its contents.

Does an sso-session profile refresh tokens forever?

No. It refreshes for as long as your Identity Center sign-in session is alive, 8 hours by default and up to 90 days if your admin allows. When the session ends, you sign in again.

How is the legacy SSO profile different from sso-session?

The legacy profile keeps sso_start_url, sso_region, sso_account_id and sso_role_name directly under [profile]. It can sign in but cannot refresh, and AWS describes it as a fixed eight-hour session. An [sso-session] block adds a refresh token and can be shared by several profiles.

Which AWS CLI version supports Identity Center session management?

Any current AWS CLI v2 supports the refreshable sso-session layout, and version 2.22.0 or later also signs in with PKCE by default. The legacy profile format still works on either major version, but it is fixed at eight hours with no refresh.

How do I run aws sso login with no browser on an SSH server?

Use aws sso login --profile my-dev --no-browser --use-device-code. The CLI prints a URL and a short code; open the URL on any device with a browser, enter the code, and return to the shell.

Can IAM Identity Center session duration stop the token expiring?

A longer user interactive session means fewer sign-ins, within your company's policy, and it applies only to new sessions. It does not make sessions unlimited, and the permission-set duration is a separate clock that does not affect sign-in at all.

Why does Terraform say the SSO token expired when the AWS CLI works?

Terraform's provider and its state backend each pick credentials on their own, possibly from a different profile, an environment variable, or an older provider that cannot read the sso-session layout. Find which stage fails and which consumer it belongs to.

Why does VS Code keep using an old SSO login?

The AWS Toolkit keeps its own connection, and remote or WSL windows may read a different home folder. Reconnect the Toolkit through its own sign-in, update it if it is old, and restart the window.

What is the difference between ExpiredToken and an SSO refresh error?

ExpiredToken is about temporary AWS credentials used to sign a request, often static ones a process was given. The SSO refresh error is about getting the Identity Center token that produces those credentials in the first place.

Can I use aws sso login in a cron job or CI pipeline?

No. It needs a person to approve the sign-in in a browser, so it will stall when nobody is there. Give unattended jobs an IAM role, an execution role, or federation on the runner.

What is my AWS SSO start URL?

It is the AWS access portal URL on your IAM Identity Center dashboard, usually https://something.awsapps.com/start. From CLI 2.22.0 the issuer URL on the same page is accepted too.

Why does the CLI return the wrong account after I sign in?

You are using a different profile from the one you think, or AWS_PROFILE points elsewhere. Run aws configure list --profile my-dev to see where each setting comes from, and read the account ID that get-caller-identity returns before running anything else.

If your morning started with this error, the right profile is usually one sign-in away, and nothing on your machine needs deleting. Jake's weekly script runs again now; his profile has a session block, the report job that used to borrow his sign-in has a role of its own, and the only time he sees the error is the honest one, when his session has ended and a sign-in is simply due. That is the shape to aim for: the person's login for the person, an identity of its own for everything else, and a two-minute check that tells you which clock stopped before you touch a single file.

📌 If you keep one line from this page

A refreshable SSO profile renews itself while your sign-in session lives. It cannot make a sign-in permanent, and the legacy profile cannot refresh at all.

Sign in, prove the identity, then fix the layout once.

Revision note. Written October 8, 2026, with PKCE sign-in as the CLI default and 90-day sessions available. If an expired token has stopped your morning, the right profile is usually one login away, and nothing else on your machine needs deleting.

Related