@@ -55,12 +55,18 @@ type Token struct {
5555}
5656
5757// 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
5960type tokenJSON struct {
6061 AccessToken string `json:"access_token"`
6162 TokenType string `json:"token_type"`
6263 RefreshToken string `json:"refresh_token"`
6364 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"`
6470}
6571
6672func (e * tokenJSON ) expiry () (t time.Time ) {
@@ -236,21 +242,29 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
236242 if err != nil {
237243 return nil , fmt .Errorf ("oauth2: cannot fetch token: %v" , err )
238244 }
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
244251 }
245252
246253 var token * Token
247254 content , _ , _ := mime .ParseMediaType (r .Header .Get ("Content-Type" ))
248255 switch content {
249256 case "application/x-www-form-urlencoded" , "text/plain" :
257+ // some endpoints return a query string
250258 vals , err := url .ParseQuery (string (body ))
251259 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 )
253264 }
265+ retrieveError .ErrorCode = vals .Get ("error" )
266+ retrieveError .ErrorDescription = vals .Get ("error_description" )
267+ retrieveError .ErrorURI = vals .Get ("error_uri" )
254268 token = & Token {
255269 AccessToken : vals .Get ("access_token" ),
256270 TokenType : vals .Get ("token_type" ),
@@ -265,8 +279,14 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
265279 default :
266280 var tj tokenJSON
267281 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 )
269286 }
287+ retrieveError .ErrorCode = tj .ErrorCode
288+ retrieveError .ErrorDescription = tj .ErrorDescription
289+ retrieveError .ErrorURI = tj .ErrorURI
270290 token = & Token {
271291 AccessToken : tj .AccessToken ,
272292 TokenType : tj .TokenType ,
@@ -276,17 +296,37 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
276296 }
277297 json .Unmarshal (body , & token .Raw ) // no error checks for optional fields
278298 }
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+ }
279305 if token .AccessToken == "" {
280306 return nil , errors .New ("oauth2: server response missing access_token" )
281307 }
282308 return token , nil
283309}
284310
311+ // mirrors oauth2.RetrieveError
285312type 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
288318}
289319
290320func (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+ }
291331 return fmt .Sprintf ("oauth2: cannot fetch token: %v\n Response: %s" , r .Response .Status , r .Body )
292332}
0 commit comments