markbates / goth

Package goth provides a simple, clean, and idiomatic way to write authentication packages for Go web applications.

Home Page:https://blog.gobuffalo.io/goth-needs-a-new-maintainer-626cd47ca37b

Repository from Github https://github.commarkbates/gothRepository from Github https://github.commarkbates/goth

No documentation on why there is a defer logout

lil5 opened this issue · comments

defer Logout(res, req)

func CompleteUserAuth
In this function, why is there a defer to Logout?


I've been trying to figure out why my use of gothic is returning a cookie (_gothic_session) with an expires of "1970-01-01T00:00:01.000Z".

I was thinking the same thing and ended up copying the whole function and commenting the defer Logout() line. I also moved the validateState(...) check after FetchUser(...) to let CompleteUserAuth(...) work for already logged-in users like in the main example.

Here's the code:

var CompleteUserAuthNoLogout = func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
	providerName, err := gothic.GetProviderName(req)
	if err != nil {
		return goth.User{}, err
	}

	provider, err := goth.GetProvider(providerName)
	if err != nil {
		return goth.User{}, err
	}

	value, err := gothic.GetFromSession(providerName, req)
	if err != nil {
		return goth.User{}, err
	}
	//HACK: Removed defer gothic.Logout(res, req)
	sess, err := provider.UnmarshalSession(value)
	if err != nil {
		return goth.User{}, err
	}

	user, err := provider.FetchUser(sess)
	if err == nil {
		// user can be found with existing session data
		return user, err
	}

	// HACK: validateState only after FetchUser fails
	err = validateState(req, sess)
	if err != nil {
		return goth.User{}, err
	}

	params := req.URL.Query()
	if params.Encode() == "" && req.Method == "POST" {
		_ = req.ParseForm()
		params = req.Form
	}

	// get new token and retry fetch
	_, err = sess.Authorize(provider, params)
	if err != nil {
		return goth.User{}, err
	}

	err = gothic.StoreInSession(providerName, sess.Marshal(), req, res)

	if err != nil {
		return goth.User{}, err
	}

	gu, err := provider.FetchUser(sess)
	return gu, err
}

func validateState(req *http.Request, sess goth.Session) error {
	rawAuthURL, err := sess.GetAuthURL()
	if err != nil {
		return err
	}

	authURL, err := url.Parse(rawAuthURL)
	if err != nil {
		return err
	}

	reqState := gothic.GetState(req)

	originalState := authURL.Query().Get("state")
	if originalState != "" && (originalState != reqState) {
		return errors.New("state token mismatch")
	}
	return nil
}

Am I doing something wrong? Should I submit this as a PR?

Since gothic uses Gorilla Sessions by default it creates a state cookie when starting authentication to verify on callback. The defer logout is meant to delete the state cookie after auth is completed. StoreInSession doesn't really work the way I would like it to. For this reason I am managing the state and session on my own. See this comment I made previously.

I also made my own CompleteUserAuth since I do not want to use the Gorilla Sessions. Since CompleteUserAuth is a var its very easy to make your own function and use that instead.

I also made a function to check the sessions validity and attempt to silently get a new access token on expiration. This is called every time an API call happens.