Properly send https Cookie - go

I'm building a small service that interacts with other server.
I must send a cookie with session token to that server. And use it later for my identification. But that cookie isn't set. And no cookies are sent back.
The request is made over https protocol.
Here's the code:
// ---> create Client
client := &http.Client{}
// ---> create Request
req, err := http.NewRequest("GET", indexURL, nil)
if err != nil {
fmt.Printf("NewRequest fail: %s\n", err)
}
// ---> define a cookie
cookie := http.Cookie{Name: "sess", Value: "value", HttpOnly: true, MaxAge: 0, Path: "/", Domain: "the.server.domain"}
req.AddCookie(&cookie)
// ---> fire in the hole!
resp, err := client.Do(req)
if err != nil {
fmt.Printf("client.Do fail: %s\n", err)
}
defer resp.Body.Close()
// ---> read the cookies
fmt.Printf("cookies: %d\n", len(resp.Cookies())) // prints zero (((
// ---> not even launched ((
for _, cookie := range resp.Cookies() {
fmt.Printf("GET cookie[%s] = %s\n", cookie.Name, cookie.Value)
}
What I am doing wrong?
I tried also
req.Header.Set(`Cookie`, `sess=value`)
but with no effect

Your code seems fine, it must be an issue with the server. Even without sending
a cookie, I can get a cookie back:
package main
import "net/http"
func main() {
r, e := http.Get("https://stackoverflow.com")
if e != nil {
panic(e)
}
defer r.Body.Close()
c := r.Cookies()
println(len(c) == 1)
}

Related

Named cookie not present

I am building a website that will rely on cookies for various things.
Then I decided to have a function that sets a cookie then read the same cookie in order to see if the browser allows cookies.
But this fails.
The template in ./views/index.html
{{define "index"}}template{{end}}
The main code:
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"strconv"
"time"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
var tmpl *template.Template
func main(){
port :=":8088"
router := mux.NewRouter()
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
//Set test cookie
cookieName := strconv.FormatInt(time.Now().UnixNano(), 10)
cookieValue := strconv.FormatInt(time.Now().UnixNano(), 10)
fmt.Println("cookieName:" + cookieName)
fmt.Println("cookieValue:" + cookieValue)
cookie := http.Cookie{Name: cookieName, Value: cookieValue, Path: "/"}
http.SetCookie(w, &cookie)
//Get cookies
fmt.Println("Range over cookies")
for _, c := range r.Cookies() {
fmt.Println(c)
}
//Get test cookie by name
c, err := r.Cookie(cookieName)
if err != nil {
fmt.Println("Error: " + err.Error())
} else {
fmt.Println(c.Value)
}
err = tmpl.ExecuteTemplate(w, "index", "")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
var err error
tmpl, err = template.ParseGlob("views/*")
if err != nil {
panic(err.Error())
}
router.PathPrefix("/").HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
http.FileServer(http.Dir("./static/")).ServeHTTP(res, req)
})
fmt.Println("Server running on localhost" + port)
err = http.ListenAndServe(port, handlers.CompressHandler(router))
if err != nil {
log.Fatal(err)
}
}
This is terminal output:
Server running on localhost:8088
cookieName:1636243636497412077
cookieValue:1636243636497413613
Range over cookies
Error: http: named cookie not present
Any pointers to what my issue might be?
You are checking r.Cookies before you have sent the cookie to the client. You must send the cookie and then if you want to check their cookie, send a second request. It would be much easier to just open the browser and look to see if your cookie is there after you send your first response.
The method Request.Cookie gets a cookie from request Cookie headers.
The function http.SetCookie adds a Set-Cookie header to the response headers. You can observe the result of http.SetCookie using this code:
fmt.Println(w.Header()["Set-Cookie"])
The named cookie is not present in the current request because http.SetCookie does not modify the current request.
The flow of cookie values is this:
The server sets cookies in a response using the Set-Cookie header.
The client stores the cookies in a "cookie jar".
The client adds matching cookies from the jar to requests using the Cookie request header.
The server gets the cookies form the request headers.
Try this code. Load the page in the browser and refresh to observe the flow of cookie values.
const cookieName = "example"
cookieValue := strconv.FormatInt(time.Now().UnixNano(), 10)
fmt.Printf("Set cookie %s=%s\n", cookieName, cookieValue)
cookie := http.Cookie{Name: cookieName, Value: cookieValue, Path: "/"}
http.SetCookie(w, &cookie)
c, err := r.Cookie(cookieName)
if err != nil {
fmt.Printf("Get cookie %s error: %v\n", cookieName, err)
} else {
fmt.Printf("Get cookie %s=%s\n", cookieName, c.Value)
}

'Request.RequestURI can't be set in client request' error when resend it through proxy

When I try to resend requests from simple proxy
http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request) {
log.Printf("proxy rq: %v", r)
client := &http.Client{}
proxyRes, err := client.Do(r) // 👈🏻 Get "http://localhost:8097/tmp": http: Request.RequestURI can't be set in client requests
if err != nil {
log.Fatalf("err proxy request: %v",err)
}
resBody := proxyRes.Body
defer resBody.Close()
if _, err := io.Copy(w, resBody); err != nil {
log.Printf("copy error:%v\n", err)
}
})
http.ListenAndServe(":8099", nil)
, with set http_proxy ENV (to send requests thought my proxy)
% export http_proxy=http://localhost:8099
% curl -v http://localhost:8097/tmp
I get an error like
Get "http://localhost:8097/tmp": http: Request.RequestURI can't be set in client requests
What did I miss?
You can't use the client request as a parameter of Do. Create a new request with the same parameter as r, then perform Do on this request
The error is defined in the ../src/net/http/client.go:217 (https://go.dev/src/net/http/client.go) as:
if req.RequestURI != "" {
req.closeBody()
return nil, alwaysFalse, errors.New("http: Request.RequestURI can't be set in client requests")
}
You should be able to reuse existing r *http.Request if you set r.RequestURI = "" before sending a HTTP request:
r.RequestURI = ""
proxyRes, err := client.Do(r)

Reuse Go http client

I want to make a get request for each param in an params array. The url is static. Is there a way to reuse my custom http client for each iteration? I don't want to reset the header for each request. Ideally, I'd like to do something like client.Do(param) for each iteration.
client := &http.Client{}
for _, param := range params {
uri := url + param
req, err := http.NewRequest(http.MethodGet, uri, nil)
req.Header.Add("Cookie", cookie)
resp, _ := client.Do(req)
defer resp.Body.Close()
// do something...
}
I think you are wanting to just keep your cookies, and not have to set it on each request? If that's the case you can do:
import (
"net/http"
"net/http/cookiejar"
"golang.org/x/net/publicsuffix"
)
// All users of cookiejar should import "golang.org/x/net/publicsuffix"
cookieJar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
panic(err)
}
var cookies []*http.Cookie
cookies = append(cookies, cookie)
u, err := url.Parse("http://whateversite.com")
if err != nil {
panic(err)
}
jar.SetCookies(u, cookies)
client := &http.Client{
Jar: cookieJar,
}

Cookie not being returned with subsequent REST calls to Go Server

I am exploring OAuth2 authentication and set up a server that authenticates with Github. I followed this example, and was able to get it working. I wanted to continue on with some of the suggestions and implement a basic session token system and make the Github calls from my server as opposed to sending the Authorization token to the client.
Here is my slightly modified /oauth/redirect handler
func oauthRedirectHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("/oauth/redirect")
err := r.ParseForm()
if err != nil {
fmt.Fprintf(os.Stdout, "could not parse query: %+v", err)
w.WriteHeader(http.StatusBadRequest)
}
code := r.FormValue("code")
reqURL := fmt.Sprintf("https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s", clientID, clientSecret, code)
req, err := http.NewRequest(http.MethodPost, reqURL, nil)
if err != nil {
fmt.Fprintf(os.Stdout, "could not create HTTP request: %v", err)
w.WriteHeader(http.StatusBadRequest)
}
req.Header.Set("accept", "application/json")
res, err := httpClient.Do(req)
if err != nil {
fmt.Fprintf(os.Stdout, "could not send HTTP request: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
}
defer res.Body.Close()
var t oAuthAccessResponse
if err := json.NewDecoder(res.Body).Decode(&t); err != nil {
fmt.Fprintf(os.Stdout, "could not parse JSON response: %+v", err)
w.WriteHeader(http.StatusBadRequest)
}
newSession := sessionTracker{
AccessToken: accessToken,
TimeOut: time.Now().Add(time.Minute * 15),
}
sessionToken := uuid.New().String()
sessionTrackerCache[sessionToken] = newSession
http.SetCookie(w, &http.Cookie{
Name: sessionTokenConst,
Value: sessionToken,
Expires: newSession.TimeOut,
Domain: "localhost",
})
http.Redirect(w, r, "/welcome.html", http.StatusFound)
}
It redirects to the welcome page with an attached cookie that includes my SessionToken id.
Here is my welcomeHandler
func welcomeHandler(w http.ResponseWriter, req *http.Request) {
fmt.Println("/welcome")
cookie, err := req.Cookie(sessionTokenConst)
if err != nil {
fmt.Fprintf(os.Stdout, "no cookie attached: %+v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
dat, err := ioutil.ReadFile("./public/welcome.html")
if err != nil {
fmt.Fprintf(os.Stdout, "could not read welcome page: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
}
fmt.Fprintf(w, string(dat))
}
Observing my browser's network tab, I authenticate with Github and my server is able to see the authorization token. The redirect response at the end of the oauthRedirectHandler contains the cookie with the SessionID. My issue lies in the fact that the browser does not seem to attach the token on the GET call to /welcome.html. I can confirm this in both the browser and in the welcomeHandler.
This is all hosted locally.
I'm not sure if this is a issue with my server, my browser, or if my understanding that cookies are applied by the browser to any future client requests until the cookie expiration date is wrong.
Any help is appreciated!
Browsers default the cookie path to the request path. Set the path to "/" to make the cookie available across the site.
Do not set the domain unless you specifically have a reason to do so (thank you Volker for noting that).
http.SetCookie(w, &http.Cookie{
Name: sessionTokenConst,
Value: sessionToken,
Path: "/", // <--- add this line
Expires: newSession.TimeOut,
// Domain: "localhost", <-- Remove this line
})

Why is the environment ignored by the httptest client?

I have this unit test for a proxy i'm writing. I cannot for the life of me see why the environment get ignored and my test make a direct access to the target server, acap.
func TestHandleHTTPS(t *testing.T) {
successfulCalls := 0
proxyPassed := 0
acap := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
successfulCalls++
}))
defer acap.Close()
testproxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
proxyPassed++
}))
defer testproxy.Close()
os.Setenv("https_proxy", testproxy.URL)
defer os.Setenv("https_proxy", "")
client := acap.Client()
tmp := client.Transport.(*http.Transport)
tmp.Proxy = http.ProxyFromEnvironment // <--- This should make client use env vars!
req, err := http.NewRequest("GET", acap.URL, nil)
if err != nil {
t.Errorf("Unable to create request: %s", err.Error())
}
resp, err := client.Do(req)
if err != nil {
t.Errorf("Something is wrong with the test: %s", err.Error())
return
}
if resp.StatusCode != 200 {
t.Errorf("Unexpected status code: %d", resp.StatusCode)
body, _ := ioutil.ReadAll(resp.Body)
t.Errorf("Body: %s", string(body))
}
if successfulCalls == 0 {
t.Errorf("No successful call over HTTPS occurred")
}
if proxyPassed == 0 {
t.Errorf("Proxy got ignored")
}
}
The only failure i get is Proxy got ignored. I use Go v1.10, everything compiles.
Edit 1:
I do the tmp.Proxy dance because the client already have certificates and stuff configured in the Transport. I don't want to mess that up by replacing the entire Transport struct
If you take a look at doc for ProxyFromEnvironment you will find there a special case:
As a special case, if req.URL.Host is "localhost" (with or without a port number), then a nil URL and nil error will be returned.
That means that no proxy will be used. I would suggest you to use ProxyURL instead
proxyURL, _ := url.Parse(testproxy.URL)
tmp.Proxy = http.ProxyURL(proxyURL)
It will take into account your Proxy, but won't work, because you are trying to make an https call throw http proxy...

Resources