Skip to content

Commit a2263fc

Browse files
committed
Add error fields to RetrieveError
Parse error parameters described in OAuth RFC https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
1 parent 68a41d6 commit a2263fc

File tree

2 files changed

+50
-10
lines changed

2 files changed

+50
-10
lines changed

internal/token.go

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,18 @@ type Token struct {
5757
}
5858

5959
// tokenJSON is the struct representing the HTTP response from OAuth2
60-
// providers returning a token in JSON form.
60+
// providers returning a token or error in JSON form.
61+
// https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
6162
type tokenJSON struct {
6263
AccessToken string `json:"access_token"`
6364
TokenType string `json:"token_type"`
6465
RefreshToken string `json:"refresh_token"`
6566
ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
67+
// error fields
68+
// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
69+
Error string `json:"error"`
70+
ErrorDescription string `json:"error_description"`
71+
ErrorUri string `json:"error_uri"`
6672
}
6773

6874
func (e *tokenJSON) expiry() (t time.Time) {
@@ -238,21 +244,29 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
238244
if err != nil {
239245
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
240246
}
241-
if code := r.StatusCode; code < 200 || code > 299 {
242-
return nil, &RetrieveError{
243-
Response: r,
244-
Body: body,
245-
}
247+
248+
failureStatus := r.StatusCode < 200 || r.StatusCode > 299
249+
retrieveError := &RetrieveError{
250+
Response: r,
251+
Body: body,
252+
// attempt to populate error detail populated below
246253
}
247254

248255
var token *Token
249256
content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
250257
switch content {
251258
case "application/x-www-form-urlencoded", "text/plain":
259+
// some endpoints such as GitHub return a query string https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#response-1
252260
vals, err := url.ParseQuery(string(body))
253261
if err != nil {
254-
return nil, err
262+
if failureStatus {
263+
return nil, retrieveError
264+
}
265+
return nil, fmt.Errorf("oauth2: cannot parse response: %v", err)
255266
}
267+
retrieveError.ErrorCode = vals.Get("error")
268+
retrieveError.ErrorDescription = vals.Get("error_description")
269+
retrieveError.ErrorCode = vals.Get("error")
256270
token = &Token{
257271
AccessToken: vals.Get("access_token"),
258272
TokenType: vals.Get("token_type"),
@@ -265,10 +279,17 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
265279
token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
266280
}
267281
default:
282+
// spec says to return JSON https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
268283
var tj tokenJSON
269284
if err = json.Unmarshal(body, &tj); err != nil {
270-
return nil, err
285+
if failureStatus {
286+
return nil, retrieveError
287+
}
288+
return nil, fmt.Errorf("oauth2: cannot parse json: %v", err)
271289
}
290+
retrieveError.ErrorCode = tj.Error
291+
retrieveError.ErrorDescription = tj.ErrorDescription
292+
retrieveError.ErrorUri = tj.ErrorUri
272293
token = &Token{
273294
AccessToken: tj.AccessToken,
274295
TokenType: tj.TokenType,
@@ -278,17 +299,27 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
278299
}
279300
json.Unmarshal(body, &token.Raw) // no error checks for optional fields
280301
}
302+
if failureStatus || retrieveError.ErrorCode != "" {
303+
return nil, retrieveError
304+
}
281305
if token.AccessToken == "" {
282306
return nil, errors.New("oauth2: server response missing access_token")
283307
}
284308
return token, nil
285309
}
286310

311+
// mirrors oauth2.RetrieveError
287312
type RetrieveError struct {
288-
Response *http.Response
289-
Body []byte
313+
Response *http.Response
314+
Body []byte
315+
ErrorCode string
316+
ErrorDescription string
317+
ErrorUri string
290318
}
291319

292320
func (r *RetrieveError) Error() string {
321+
if r.ErrorCode != "" {
322+
return fmt.Sprintf("oauth2: cannot fetch token: %s %s %s", r.ErrorCode, r.ErrorDescription, r.ErrorUri)
323+
}
293324
return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body)
294325
}

token.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,17 @@ type RetrieveError struct {
171171
// Body is the body that was consumed by reading Response.Body.
172172
// It may be truncated.
173173
Body []byte
174+
// rfc6749 error parameter https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
175+
ErrorCode string
176+
// rfc6749 error_description parameter https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
177+
ErrorDescription string
178+
// rfc6749 error_uri parameter https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
179+
ErrorUri string
174180
}
175181

176182
func (r *RetrieveError) Error() string {
183+
if r.ErrorCode != "" {
184+
return fmt.Sprintf("oauth2: cannot fetch token: %s %s %s", r.ErrorCode, r.ErrorDescription, r.ErrorUri)
185+
}
177186
return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body)
178187
}

0 commit comments

Comments
 (0)