···8181}
82828383// Helper method to handle DPoP retries and client assertions (if the client is confidential)
8484-// body object will be url-encoded (can be InitialTokenRequest, RefreshTokenRequest, RevocationRequest)
8484+// body object will be url-encoded (expected to be either RefreshTokenRequest or RevocationRequest)
8585// expects sess.lk to be held by caller
8686-// on success, caller is responsible for closing the response body
8686+// if a non-nil *http.Response is returned, the caller is responsible for closing the response body
8787func (sess *ClientSession) postToAuthServer(ctx context.Context, url string, body interface{}) (*http.Response, error) {
8888 vals, err := query.Values(body)
8989 if err != nil {
···155155 GrantType: "refresh_token",
156156 RefreshToken: sess.Data.RefreshToken,
157157 }
158158- tokenURL := sess.Data.AuthServerTokenEndpoint
159158160160- if sess.Config.IsConfidential() {
161161- clientAssertion, err := sess.Config.NewClientAssertion(sess.Data.AuthServerURL)
162162- if err != nil {
163163- return "", err
164164- }
165165- body.ClientAssertionType = &ClientAssertionJWTBearer
166166- body.ClientAssertion = &clientAssertion
167167- }
168168-169169- vals, err := query.Values(body)
159159+ resp, err := sess.postToAuthServer(ctx, sess.Data.AuthServerTokenEndpoint, body)
170160 if err != nil {
171171- return "", err
172172- }
173173- bodyBytes := []byte(vals.Encode())
174174-175175- var resp *http.Response
176176- for range 2 {
177177- dpopJWT, err := NewAuthDPoP("POST", sess.Data.AuthServerTokenEndpoint, sess.Data.DPoPAuthServerNonce, sess.DPoPPrivateKey)
178178- if err != nil {
179179- return "", err
180180- }
181181-182182- req, err := http.NewRequestWithContext(ctx, "POST", tokenURL, bytes.NewBuffer(bodyBytes))
183183- if err != nil {
184184- return "", err
185185- }
186186- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
187187- req.Header.Set("DPoP", dpopJWT)
188188-189189- resp, err = sess.Client.Do(req)
190190- if err != nil {
191191- return "", err
192192- }
193193-194194- // always check if a new DPoP nonce was provided, and proactively update session data (even if there was not an explicit error)
195195- dpopNonceHdr := resp.Header.Get("DPoP-Nonce")
196196- if dpopNonceHdr != "" && dpopNonceHdr != sess.Data.DPoPAuthServerNonce {
197197- sess.Data.DPoPAuthServerNonce = dpopNonceHdr
198198- }
199199-200200- // check for an error condition caused by an out of date DPoP nonce
201201- // note that the HTTP status code is 400 Bad Request on the Auth Server token endpoint, not 401 Unauthorized like it would be on Resource Server requests
202202- if resp.StatusCode == http.StatusBadRequest && dpopNonceHdr != "" {
203203- // parseAuthErrorReason() always closes resp.Body
204204- reason := parseAuthErrorReason(resp, "token-refresh")
205205- if reason == "use_dpop_nonce" {
206206- // already updated nonce value above; loop around and try again
207207- continue
208208- }
209209- return "", fmt.Errorf("token refresh failed (HTTP %d): %s", resp.StatusCode, reason)
210210- }
211211-212212- // otherwise process response (success or other error type)
213213- break
161161+ return "", fmt.Errorf("token refresh failed: %w", err)
214162 }
215163216164 defer resp.Body.Close()