Cookies discarded from challenge response
mizhousoft opened this issue · comments
Thanks for your code, solved my proble.
But there is a problem,post the second url with Authorization header, it will be fail. because the server has given up the nonce paramter.
so I think post the second url with cookie header.
I modify the code:
- add cookiejar.go file
type HttpCookieJar struct {
cookies []*http.Cookie
}
func (jar *HttpCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
jar.cookies = cookies
}
func (jar *HttpCookieJar) Cookies(u *url.URL) []*http.Cookie {
return jar.cookies
}
- modify transport.go file
func (t *Transport) clear(res *http.Response) error {
host := res.Request.URL.Hostname()
t.cacheMu.Lock()
if t.cache == nil {
t.cache = map[string]*cached{}
}
delete(t.cache, host)
t.cacheMu.Unlock()
return nil
}
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
// use the configured transport if there is one
tr := t.Transport
if tr == nil {
tr = http.DefaultTransport
}
// don't modify the original request
clone, err := cloner(req)
if err != nil {
return nil, err
}
// setup the first request
first, err := clone()
if err != nil {
return nil, err
}
// try to authorize the request using a cached challenge
if err := t.authorize(first); err != nil {
return nil, err
}
// the first request will either succeed or return a 401
res, err := tr.RoundTrip(first)
if err != nil || res.StatusCode != http.StatusUnauthorized {
return res, err
}
// close the first message body
res.Body.Close()
// save the challenge for future use
if err := t.save(res); err != nil {
return nil, err
}
// setup the second request
second, err := clone()
if err != nil {
return nil, err
}
// authorise a second request based on the new challenge
if err := t.authorize(second); err != nil {
return nil, err
}
resp, error := tr.RoundTrip(second)
t.clear(resp)
return resp, error
}
3 Test demo
// Source code from https://github.com/icholy/digest
func TestHttpDigest(t *testing.T) {
jar := new(HttpCookieJar)
client := http.Client{
Transport: &Transport{
Username: "test",
Password: "test",
},
Jar: jar,
}
res, _ := client.Get("http://localhost:8080/demo/user.action")
println(res.Status)
res, _ = client.Get("http://localhost:8080/demo/test.action")
println(res.Status)
}
It's legal to re-use a nonce (this prevents 2 round-trips for every request). The server can reject it and provide a new challenge. What error are you seeing? The stdlib already implements a cookiejar https://golang.org/pkg/net/http/cookiejar/
http client post the second url (http://localhost:8080/demo/test.action) with Authorization, the server will reject, because it is not safe. so must re-authenticate.
The cookiejar https://golang.org/pkg/net/http/cookiejar/ is very good.
That’s how digest auth works. Most servers will let you re-use a nonce for a period of time before invalidating it. Wireshark your browser and you’ll see the same thing. If you want something safe, don’t use digest auth.
Yes, I use spring security as server digest auth, I read spring securitysource code, the nonce validity second is 300 second.
Thanks.
I'm actually facing the same issue when the server is using Flask. Same thing happens from Postman. The 2nd request fails unless I clear the cookie.
But doing a request from Go using this lib fails on first try too.
Please read the thread. It's not an issue, it's how digest auth works.
So it's expected not to work with the flash-auth digest impl.? Or you're only talking about the 2nd req fail?
The first request should always fail assuming the client doesn't have a cached challenge from a previous failed request. Some servers don't support re-using the challenge, so every other request will fail (to get the new challenge). This should all happen behind the scenes automatically.
edit: is your request not going through at all? If so, open a separate issue, because it's unrelated.
The first request should always fail assuming the client doesn't have a cached challenge from a previous failed request. Some servers don't support re-using the challenge, so every other request will fail (to get the new challenge). This should all happen behind the scenes automatically.
edit: is your request not going through at all? If so, open a separate issue, because it's unrelated.
Yes, I'd expect the first request to fail, then after I get a challenge, I'd expect it to go through. But I can see 401 on both requests from the Go client.
I'm using a pretty basic setup of Flask. It looks like:
- https://flask.palletsprojects.com/en/2.0.x/quickstart/#a-minimal-application
- https://flask-httpauth.readthedocs.io/en/latest/ - using digest auth
- A simple Go client that uses this lib for auth (very much like your example
I can provide a more accurate explanation of what I do later on as I'm not at my workstation.
Copy/paste that into a new issue. This sounds like it might be a real bug.
@rolandjitsu the problem is that flask auth is trying to set cookies as part of the challenge response. That response never makes it out of the transport, so the http.Client
doesn't have a chance to add it to its http.(*Client).Jar
. I just pushed version v0.1.11
which allows you to specify an http.CookieJar
in the digest.Transport
.
package main
import (
"net/http"
"net/http/cookiejar"
"github.com/icholy/digest"
)
func main() {
jar, _ := cookiejar.New(nil)
client := &http.Client{
Jar: jar,
Transport: &digest.Transport{
Jar: jar,
Username: "foo",
Password: "bar",
},
}
res, err := client.Get("http://localhost:8080/some_outdated_service")
if err != nil {
panic(err)
}
defer res.Body.Close()
}
@rolandjitsu the problem is that flask auth is trying to set cookies as part of the challenge response. That response never makes it out of the transport, so the
http.Client
doesn't have a chance to add it to itshttp.(*Client).Jar
. I just pushed versionv0.1.11
which allows you to specify anhttp.CookieJar
in thedigest.Transport
.
@icholy thanks!
@icholy just tested the fix and I can confirm it works.