-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refresh token expiry ignored #397
Comments
I am encountering this same exact issue now 8 months later. Is there any way to get around this problem? |
I just encountered this. Our oauth2 client kept trying to use the refresh token for 4 days beyond the expiration, so it never got a valid token once it got into this state (it had run for weeks without encountering this problem). Strangely, it had used a valid oauth2 token just 1 minute before this happened. Timeline looks like this: 2020-06-18 19:46:57 - successfully uses access token From what I can tell, the oauth2 module would have never recovered from this state; it would keep presenting an expired refresh token to the server in perpetuity. We had to bounce the application to get it to start over. Any workaround? |
After investigating to reuse the same My workaround: when having an error during a The ideal solution: if the library is able to send on upper layers the response (or a part of) of the provider, so we can detect or not if the refreshing has failed due to expired refresh token. But... it would be a real custom solution because each "OAuth2/OIDC providers" has their own logic to notified of this... too specific I agree. I don't see a perfect solution for now 😞 . Would be interested to your thoughts as main maintainers @ScruffyProdigy @rakyll @bradfitz ? Thank you, |
The middle of 2022... any solution for that? |
From what I see in RFC (https://datatracker.ietf.org/doc/html/rfc6749#section-5.1), refresh token expiration timeout is not shared with a client, so there's no timer you could potentially rely on. Also, a refresh token can be revoked due to multiple reasons:
It makes reliance on an expiration timer not realistic as the validity depends on internal settings of an IDP. Refresh token may be present in a successful response (again, the section 5.1 of the RFC), and I think if there's a rotation done by an IDP, the change will be reflected by the library: func (tf *tokenRefresher) Token() (*Token, error) {
if tf.refreshToken == "" {
return nil, errors.New("oauth2: token expired and refresh token is not set")
}
tk, err := retrieveToken(tf.ctx, tf.conf, url.Values{
"grant_type": {"refresh_token"},
"refresh_token": {tf.refreshToken},
})
if err != nil {
return nil, err
}
if tf.refreshToken != tk.RefreshToken {
tf.refreshToken = tk.RefreshToken
}
return tk, err
} Section 5.2 of the RFC (https://datatracker.ietf.org/doc/html/rfc6749#section-5.2) defines error types, amongst which
That's the error that the topic starter shared: func TestTokenRetrieveError(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.String() != "/token" {
t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL)
}
w.Header().Set("Content-type", "application/json")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error": "invalid_grant"}`))
}))
defer ts.Close()
conf := newConf(ts.URL)
_, err := conf.Exchange(context.Background(), "exchange-code")
if err == nil {
t.Fatalf("got no error, expected one")
}
_, ok := err.(*RetrieveError)
if !ok {
t.Fatalf("got %T error, expected *RetrieveError; error was: %v", err, err)
}
// Test error string for backwards compatibility
expected := fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", "400 Bad Request", `{"error": "invalid_grant"}`)
if errStr := err.Error(); errStr != expected {
t.Fatalf("got %#v, expected %#v", errStr, expected)
}
} Given the fact that Oauth2 defines different flows, and "Authorization Code Grant" flow requires user interaction like on the diagram here: https://datatracker.ietf.org/doc/html/rfc6749#section-4.1, IMO, it would be too much for the library to handle that. Instead, you're expected to inspect the error messages returned by the library, and if you see As a final note, please, note that I'm not an Oauth2 expert, my investigation of the issue might not be entirely correct. |
Well, as a workaround I was using a forked version of golang oauth2 package with added and exported refresh token expiry fields. Plus, wrote and used a package for renewing refresh token before expiration. // expired reports whether the token is expired.
// t must be non-nil.
func (t *Token) expired() bool {
if t.Expiry.IsZero() {
return false
}
return t.Expiry.Round(0).Add(-expiryDelta).Before(timeNow())
}
// Valid reports whether t is non-nil, has an AccessToken, and is not expired.
func (t *Token) Valid() bool {
return t != nil && t.AccessToken != "" && !t.expired()
} And that makes sense: why would I want to make another http(s) request to receive |
@dimandzhi ah, I wasn't aware that there's |
Two years later again...No work here, Bug is still open.... |
What version of Go are you using (
go version
)?go version go1.12.7 linux/amd64
What operating system and processor architecture are you using?
Linux version 4.15.0-60-generic (buildd@lgw01-amd64-030) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) #67-Ubuntu SMP Thu Aug 22 16:55:30 UTC 2019
What did you do?
What did you expect to see?
Usual workflow with
http.Client
usage and usual http/connection error handling.What did you see instead?
After 30 minutes got
oauth2: cannot fetch token: 400 Bad Request Response: {"error":"invalid_grant","error_description":"Refresh token expired"}
error.Not only access token may have expiration time, but also refresh token itself. Currently, access token is automatically refreshed and its expiration time is exported as field. Unfortunately, there is no such logic for refresh token itself, which makes long-term usage of constructed
Token
or correspondinghttp.Client
...complicated.Simply adding
RefreshExpiresIn int32 `json:"refresh_expires_in"`
field totokenJSON
andRefreshExpiry
field toToken
will give programmer ability to handle this scenario.Also, adding such handler, automated even, to package would be even grater improvement.
truncated
.../openid-connect/token
response exampleThe text was updated successfully, but these errors were encountered: