cookiesgocookiejar

Use Golang to login to private site and pull info


I try to use golang to login in a private area of a website and pull some info, but i don't quite seem to get it right. I manage to fetch the login page to get the csrf token, then i post the csrf token together with the login info to the login page and i login just fine. If i stop at this point, i can see the page where i am redirected. However, any subsequent calls from this point on will redirect me back to login.

The code

package main

import (
    "github.com/PuerkitoBio/goquery"
    "io"
    _ "io/ioutil"
    "log"
    "net/http"
    "net/url"
    _ "strings"
    "sync"
)

type Jar struct {
    sync.Mutex
    cookies map[string][]*http.Cookie
}

func NewJar() *Jar {
    jar := new(Jar)
    jar.cookies = make(map[string][]*http.Cookie)
    return jar
}

func (jar *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
    jar.Lock()
    jar.cookies[u.Host] = cookies
    jar.Unlock()
}

func (jar *Jar) Cookies(u *url.URL) []*http.Cookie {
    return jar.cookies[u.Host]
}

func NewJarClient() *http.Client {
    return &http.Client{
        Jar: NewJar(),
    }
}

func fetch(w http.ResponseWriter, r *http.Request) {

    // create the client
    client := NewJarClient()

    // get the csrf token
    req, _ := http.NewRequest("GET", "http://www.domain.com/login", nil)
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }

    doc, err := goquery.NewDocumentFromResponse(resp)
    if err != nil {
        log.Fatal(err)
    }

    csrfToken := ""
    if val, ok := doc.Find(`head meta[name="csrf-token-value"]`).Attr("content"); ok {
        csrfToken = val
    }

    // post on the login form.
    resp, _ = client.PostForm("http://www.domain.com/login", url.Values{
        "UserLogin[email]":    {"the email"},
        "UserLogin[password]": {"the password"},
        "csrf_token":          {csrfToken},
    })

    doc, err = goquery.NewDocumentFromResponse(resp)
    if err != nil {
        log.Fatal(err)
    }

    // if i stop here then i can see just fine the dashboard where i am redirected after login.
    // but if i continue and request a 3rd page, then i get the login page again,
    // sign that i lose the cookies and i am redirected back

    // html, _ := doc.Html()
    // io.WriteString(w, html)
    // return

    // from this point on, any request will give me the login page once again.
    // i am not sure why since the cookies should be set and sent on all requests
    req, _ = http.NewRequest("GET", "http://www.domain.com/dashboard", nil)
    resp, err = client.Do(req)
    if err != nil {
        log.Fatal(err)
    }

    doc, err = goquery.NewDocumentFromResponse(resp)
    if err != nil {
        log.Fatal(err)
    }

    html, _ := doc.Html()
    io.WriteString(w, html)
}

func main() {
    http.HandleFunc("/", fetch)
    http.ListenAndServe("127.0.0.1:49721", nil)
}

Any idea what i am missing here ?


Solution

  • Ok, the issue is the cookie jar implementation, more specifically the SetCookies function, which right now is:

    func (jar *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
        jar.Lock()
        jar.cookies[u.Host] = cookies
        jar.Unlock()
    }
    

    And this is wrong because new cookies instead of being added to the existing ones they will simply be added as new discarding the old ones.

    It seems the right way to do this is:

    func (jar *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
        jar.Lock()
        if _, ok := jar.cookies[u.Host]; ok {
            for _, c := range cookies {
                jar.cookies[u.Host] = append(jar.cookies[u.Host], c)
            }
        } else {
            jar.cookies[u.Host] = cookies
        }
        jar.Unlock()
    }