Change:
} } <ins> func TestAzureTokenSourceScenarios(t *testing.T) { configMode := configModeDefault expiredToken := newFakeAzureToken("expired token", time.Now().Add(-time.Second)) extendedToken := newFakeAzureToken("extend token", time.Now().Add(1000*time.Second)) fakeToken := newFakeAzureToken("fake token", time.Now().Add(1000*time.Second)) wrongToken := newFakeAzureToken("wrong token", time.Now().Add(1000*time.Second)) tests := []struct { name         string sourceToken  *azureToken refreshToken *azureToken cachedToken  *azureToken configToken  *azureToken expectToken  *azureToken tokenErr     error refreshErr   error expectErr    string tokenCalls   uint refreshCalls uint persistCalls uint }{ { name:         "new config", sourceToken:  fakeToken, expectToken:  fakeToken, tokenCalls:   1, persistCalls: 1, }, { name:        "load token from cache", sourceToken: wrongToken, cachedToken: fakeToken, configToken: wrongToken, expectToken: fakeToken, }, { name:        "load token from config", sourceToken: wrongToken, configToken: fakeToken, expectToken: fakeToken, }, { name:         "cached token timeout, extend success, config token should never load", cachedToken:  expiredToken, refreshToken: extendedToken, configToken:  wrongToken, expectToken:  extendedToken, refreshCalls: 1, persistCalls: 1, }, { name:         "config token timeout, extend failure, acquire new token", configToken:  expiredToken, refreshErr:   fakeTokenRefreshError{message: "FakeError happened when refreshing"}, sourceToken:  fakeToken, expectToken:  fakeToken, refreshCalls: 1, tokenCalls:   1, persistCalls: 1, }, { name:         "unexpected error when extend", configToken:  expiredToken, refreshErr:   errors.New("unexpected refresh error"), sourceToken:  fakeToken, expectErr:    "unexpected refresh error", refreshCalls: 1, }, { name:       "token error", tokenErr:   errors.New("tokenerr"), expectErr:  "tokenerr", tokenCalls: 1, }, { name:        "Token() got expired token", sourceToken: expiredToken, expectErr:   "newly acquired token is expired", tokenCalls:  1, }, { name:        "Token() got nil but no error", sourceToken: nil, expectErr:   "unable to acquire token", tokenCalls:  1, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { persister := newFakePersister() cfg := map[string]string{} if tc.configToken != nil { cfg = token2Cfg(tc.configToken) } tokenCache := newAzureTokenCache() if tc.cachedToken != nil { tokenCache.setToken(azureTokenKey, tc.cachedToken) } fakeSource := fakeTokenSource{ token:        tc.sourceToken, tokenErr:     tc.tokenErr, refreshToken: tc.refreshToken, refreshErr:   tc.refreshErr, } tokenSource := newAzureTokenSource(&fakeSource, tokenCache, cfg, configMode, &persister) token, err := tokenSource.Token() if fakeSource.tokenCalls != tc.tokenCalls { t.Errorf("expecting tokenCalls: %v, got: %v", tc.tokenCalls, fakeSource.tokenCalls) } if fakeSource.refreshCalls != tc.refreshCalls { t.Errorf("expecting refreshCalls: %v, got: %v", tc.refreshCalls, fakeSource.refreshCalls) } if persister.calls != tc.persistCalls { t.Errorf("expecting persister calls: %v, got: %v", tc.persistCalls, persister.calls) } if tc.expectErr != "" { if !strings.Contains(err.Error(), tc.expectErr) { t.Errorf("expecting error %v, got %v", tc.expectErr, err) } if token != nil { t.Errorf("token should be nil in err situation, got %v", token) } } else { if err != nil { t.Fatalf("error should be nil, got %v", err) } if token.token.AccessToken != tc.expectToken.token.AccessToken { t.Errorf("token should have accessToken %v, got %v", token.token.AccessToken, tc.expectToken.token.AccessToken) } } }) } } </ins> type fakePersister struct { lock  sync.Mutex cache map[string]string <ins> calls uint } func newFakePersister() fakePersister { return fakePersister{cache: make(map[string]string), calls: 0} </ins> } func (p *fakePersister) Persist(cache map[string]string) error { p.lock.Lock() defer p.lock.Unlock() <ins> p.calls++ </ins> p.cache = map[string]string{} for k, v := range cache { p.cache[k] = v

Discussion:
<!-- Please only use this template for submitting enhancement requests --What would you like to be : Currently, when AAD login is configured, refresh token timeout would make kubectl print an error, not able to re-login without manually remove the tokens. Expect kubectl to promote user to login again. Why is this needed: Configure refresh-token timeout from Azure is a way to force re-login after inactivity. kubectl doesn't promote re-login makes users harder to apply timeout policy.
/sig azure
The label(s) cannot be appled. These labels are supported: <detailsIn response to : /sig azure Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
The label(s) cannot be appled. These labels are supported: <detailsIn response to : /sig platform/azure Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/area provider/azure
the root cause of this issue is in this first gets the cached token (result of device code flow) at it's trying to refresh an expired token at and failed. An error is returned. Since the expired token is still in the cache, it will always hit this path until user removes the token from kubeconfig. the fix should be: the token from cache a retry to re-initiate the device code flow in
The label(s) cannot be appled. These labels are supported: <detailsIn response to : /sig platform/azure Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/sig cli These SIGs are my best guesses for this issue. Please comment if I am incorrect about one. I am a bot run by vllry. ‍
I'm working on it.
are you working on it? I will grab it if I dont hear it from you :)
have the code ready will try PR this weekend Best, Ping
/assign
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close.

If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
/remove-lifecycle stale
Closing as merged