@@ -55,12 +55,18 @@ type Token struct {
55
55
}
56
56
57
57
// tokenJSON is the struct representing the HTTP response from OAuth2
58
- // providers returning a token in JSON form.
58
+ // providers returning a token or error in JSON form.
59
+ // https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
59
60
type tokenJSON struct {
60
61
AccessToken string `json:"access_token"`
61
62
TokenType string `json:"token_type"`
62
63
RefreshToken string `json:"refresh_token"`
63
64
ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
65
+ // error fields
66
+ // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
67
+ ErrorCode string `json:"error"`
68
+ ErrorDescription string `json:"error_description"`
69
+ ErrorURI string `json:"error_uri"`
64
70
}
65
71
66
72
func (e * tokenJSON ) expiry () (t time.Time ) {
@@ -236,21 +242,29 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
236
242
if err != nil {
237
243
return nil , fmt .Errorf ("oauth2: cannot fetch token: %v" , err )
238
244
}
239
- if code := r .StatusCode ; code < 200 || code > 299 {
240
- return nil , & RetrieveError {
241
- Response : r ,
242
- Body : body ,
243
- }
245
+
246
+ failureStatus := r .StatusCode < 200 || r .StatusCode > 299
247
+ retrieveError := & RetrieveError {
248
+ Response : r ,
249
+ Body : body ,
250
+ // attempt to populate error detail below
244
251
}
245
252
246
253
var token * Token
247
254
content , _ , _ := mime .ParseMediaType (r .Header .Get ("Content-Type" ))
248
255
switch content {
249
256
case "application/x-www-form-urlencoded" , "text/plain" :
257
+ // some endpoints return a query string
250
258
vals , err := url .ParseQuery (string (body ))
251
259
if err != nil {
252
- return nil , err
260
+ if failureStatus {
261
+ return nil , retrieveError
262
+ }
263
+ return nil , fmt .Errorf ("oauth2: cannot parse response: %v" , err )
253
264
}
265
+ retrieveError .ErrorCode = vals .Get ("error" )
266
+ retrieveError .ErrorDescription = vals .Get ("error_description" )
267
+ retrieveError .ErrorURI = vals .Get ("error_uri" )
254
268
token = & Token {
255
269
AccessToken : vals .Get ("access_token" ),
256
270
TokenType : vals .Get ("token_type" ),
@@ -265,8 +279,14 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
265
279
default :
266
280
var tj tokenJSON
267
281
if err = json .Unmarshal (body , & tj ); err != nil {
268
- return nil , err
282
+ if failureStatus {
283
+ return nil , retrieveError
284
+ }
285
+ return nil , fmt .Errorf ("oauth2: cannot parse json: %v" , err )
269
286
}
287
+ retrieveError .ErrorCode = tj .ErrorCode
288
+ retrieveError .ErrorDescription = tj .ErrorDescription
289
+ retrieveError .ErrorURI = tj .ErrorURI
270
290
token = & Token {
271
291
AccessToken : tj .AccessToken ,
272
292
TokenType : tj .TokenType ,
@@ -276,17 +296,37 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
276
296
}
277
297
json .Unmarshal (body , & token .Raw ) // no error checks for optional fields
278
298
}
299
+ // according to spec, servers should respond status 400 in error case
300
+ // https://www.rfc-editor.org/rfc/rfc6749#section-5.2
301
+ // but some unorthodox servers respond 200 in error case
302
+ if failureStatus || retrieveError .ErrorCode != "" {
303
+ return nil , retrieveError
304
+ }
279
305
if token .AccessToken == "" {
280
306
return nil , errors .New ("oauth2: server response missing access_token" )
281
307
}
282
308
return token , nil
283
309
}
284
310
311
+ // mirrors oauth2.RetrieveError
285
312
type RetrieveError struct {
286
- Response * http.Response
287
- Body []byte
313
+ Response * http.Response
314
+ Body []byte
315
+ ErrorCode string
316
+ ErrorDescription string
317
+ ErrorURI string
288
318
}
289
319
290
320
func (r * RetrieveError ) Error () string {
321
+ if r .ErrorCode != "" {
322
+ s := fmt .Sprintf ("oauth2: %q" , r .ErrorCode )
323
+ if r .ErrorDescription != "" {
324
+ s += fmt .Sprintf (" %q" , r .ErrorDescription )
325
+ }
326
+ if r .ErrorURI != "" {
327
+ s += fmt .Sprintf (" %q" , r .ErrorURI )
328
+ }
329
+ return s
330
+ }
291
331
return fmt .Sprintf ("oauth2: cannot fetch token: %v\n Response: %s" , r .Response .Status , r .Body )
292
332
}
0 commit comments