Skip to content
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

Possible Authorization header bug in v2.16.4 #959

Closed
danversjflett opened this issue Jan 22, 2025 · 8 comments · Fixed by #960
Closed

Possible Authorization header bug in v2.16.4 #959

danversjflett opened this issue Jan 22, 2025 · 8 comments · Fixed by #960
Assignees
Labels
bug v2 For resty v2

Comments

@danversjflett
Copy link

danversjflett commented Jan 22, 2025

Hi. I've just updated Resty in my project to v2.16.4.

We use bearer token authorization headers in our project.
We set up the Resty client using:
SetAuthScheme("Bearer").SetAuthToken(token)

This has worked for us up to v2.16.3.

Now we are getting Status Code 401 Unauthorized from the API. I can set a breakpoint and debug the code after the response is received - I see that the Resty client has the "Token" field set to the token we give it, but in the request the "Authorization" Header is not set at all.

In previous versions, the "Authorization" Header in a request made by the client is set to "Bearer [token]".

@jeevatkm
Copy link
Member

@danversjflett Thanks for reaching out. I'm unable to reproduce it, can you double check and share feedback?

package main

import (
	"fmt"

	"github.com/go-resty/resty/v2"
)

func main() {
	c := resty.New()

	res, err := c.R().
		SetBody(map[string]string{
			"name": "Resty",
		}).
		SetAuthScheme("Bearer"). // FYI, this is default value
		SetAuthToken("token-here").
		Post("https://httpbin.org/post")

	fmt.Println(err, res)
}

Output:

<nil> {
    "args": {},
    "data": "{\"name\":\"Resty\"}",
    "files": {},
    "form": {},
    "headers": {
        "Accept-Encoding": "gzip",
        "Authorization": "Bearer token-here",
        "Content-Length": "16",
        "Content-Type": "application/json",
        "Host": "httpbin.org",
        "User-Agent": "go-resty/2.16.4 (https://github.com/go-resty/resty)",
        "X-Amzn-Trace-Id": "Root=1-679045ae-691f43e53c82983032d3af3f"
    },
    "json": {
        "name": "Resty"
    },
    "origin": "0.0.0.0",
    "url": "https://httpbin.org/post"
}

@danversjflett
Copy link
Author

@jeevatkm Thanks for looking at this. In the example code you give, the Authorization token is added unconditionally.

In our code, the authorization token is added with a Retry Hook.

This code is closer to how we use Resty:

package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/go-resty/resty/v2"
)

var c *resty.Client

func main() {
	c = resty.New().
		SetHeader("Content-Type", "application/json").
		SetHeader("User-Agent", "my-project/my-service").
		SetAuthScheme("Bearer").
		SetRetryCount(3).
		SetRetryWaitTime(time.Millisecond * 350).
		SetTimeout(time.Second * 15).
		AddRetryHook(generateNewTokenRetryHook).
		AddRetryCondition(func(response *resty.Response, err error) bool {
			if err != nil {
				return true
			}

			switch response.StatusCode() {
			case
				http.StatusUnauthorized,
				http.StatusInternalServerError,
				http.StatusBadGateway,
				http.StatusServiceUnavailable,
				http.StatusGatewayTimeout:
				return true
			}

			return false
		}).
		EnableTrace()

	res, err := c.R().
		Get("https://httpbin.org/get")

	fmt.Println(err)

	spew.Dump(res)
}

func generateNewTokenRetryHook(response *resty.Response, err error) {
	if response.StatusCode() != http.StatusUnauthorized {
		return
	}

	token := "token-here" // token generation code called here

	c.SetAuthToken(token)
}

I can't use this example to exactly demonstrate the issue because httpbin doesn't give us a StatusUnauthorized response like our API does. But hopefully it gets us closer.

@danversjflett
Copy link
Author

danversjflett commented Jan 22, 2025

@jeevatkm I've found a way to recreate this, by getting httpbin to randomly return a 200 or 401 status code:

package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/go-resty/resty/v2"
)

var c *resty.Client

func main() {
	c = resty.New().
		SetHeader("Content-Type", "application/json").
		SetHeader("User-Agent", "my-project/my-service").
		SetAuthScheme("Bearer").
		SetRetryCount(3).
		SetRetryWaitTime(time.Millisecond * 350).
		SetTimeout(time.Second * 15).
		AddRetryHook(generateNewTokenRetryHook).
		AddRetryCondition(func(response *resty.Response, err error) bool {
			if err != nil {
				return true
			}

			switch response.StatusCode() {
			case
				http.StatusUnauthorized,
				http.StatusInternalServerError,
				http.StatusBadGateway,
				http.StatusServiceUnavailable,
				http.StatusGatewayTimeout:
				return true
			}

			return false
		}).
		EnableTrace()

	res, err := c.R().
		Get("https://httpbin.org/status/200,401")

	fmt.Println(err)

	fmt.Println("Final Response Status:", res.StatusCode())
	fmt.Println("Final Request Header:", res.Request.Header)
}

func generateNewTokenRetryHook(response *resty.Response, err error) {
	fmt.Println("Got Response: ", response.StatusCode(), "retrying...")

	if response.StatusCode() != http.StatusUnauthorized {
		return
	}

	token := "token-here" // token generation code called here

	c.SetAuthToken(token)
}

Output with Resty v2.16.3:

Got Response:  401 retrying...
<nil>
Final Response Status: 200
Final Request Header: map[Accept:[application/json] Authorization:[Bearer token-here] Content-Type:[application/json] User-Agent:[my-project/my-service]]

Output with Resty v2.16.4:

Got Response:  401 retrying...
<nil>
Final Response Status: 200
Final Request Header: map[Accept:[application/json] Content-Type:[application/json] User-Agent:[my-project/my-service]]

Note that the outward behavior is the same but the request header in v2.16.4 has no Authorization header.

@jeevatkm
Copy link
Member

@danversjflett Thanks for your code snippet. I found the root cause. It seems your implementation uses only client-level methods, not request-level like my example above.

With this recent 4eae633 commit, this is the side effect of it. Technically, the following line could solve your issue -

response.Request.SetAuthToken(token)

However, library behavior should not change in the patch version. I will fix it.

@jeevatkm
Copy link
Member

@danversjflett Can you try this branch fix-client-level-token and share your feedback?

@jeevatkm
Copy link
Member

@danversjflett, did you get a chance to validate it?

@danversjflett
Copy link
Author

@jeevatkm I just tested it with the example code and in our production code and the problem appears to be fixed. Thank you.

@jeevatkm
Copy link
Member

@danversjflett Thanks for the confirmation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug v2 For resty v2
Development

Successfully merging a pull request may close this issue.

2 participants