markbates / markbates/goth

cookie not issued on prod.

Open
#603 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
6.6k
Forks
631
PR merge metrics
No merged PRs in 30d

Description

it works greate locally but in prod cookie doesnt get issued over https:

here is my google handler function:

func GoogleCallbackHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Println("GoogleAuthHandler called.")
		//log all headers before the redirect
		for key, values := range w.Header() {
			for _, value := range values {
				log.Printf("Header: %s=%s", key, value)
			}
		}
		// Check for the "code" query parameter.
		code := r.URL.Query().Get("code")
		if code == "" {
			// User canceled the Google account selection.
			log.Println("Google authentication canceled by user (no code parameter).")

			// Redirect to a safe location or display an error.
			http.Redirect(w, r, "/", http.StatusFound) // Redirect to home page.

			return // Crucial: Stop further execution.
		}

		// Finalize the authentication process (only if "code" is present).
		user, err := gothic.CompleteUserAuth(w, r)
		if err != nil {
			http.Error(w, "Authentication failed", http.StatusUnauthorized)
			log.Println(err)
			return
		}

		// Directly perform FindOrCreateUser logic here
		ctx := r.Context()
		userEmail := user.Email
		userID := uuid.New().String()
		log.Println("id", userID)

		// Assuming db.DB is a *pgxpool.Pool

		// Check if user exists
		query := `SELECT id FROM "User" WHERE email = $1`
		err = db.DB.QueryRow(ctx, query, userEmail).Scan(&userID)
		if err == nil {
			// User exists, userID is already set
			log.Println("skipped...")
		} else if err.Error() == "no rows in result set" { // Use err.Error() for pgxpool
			// User does not exist, create new user
			query = `INSERT INTO "User" (id, "fullName", email, "avatarUrl") VALUES ($1, $2, $3, $4)` // Include id
			_, err = db.DB.Exec(ctx, query, userID, user.Name, user.Email, user.AvatarURL)            // use Exec since we already have the id
			if err != nil {
				http.Error(w, "Database error", http.StatusInternalServerError)
				log.Println(err)
				return
			}

			// Set User Preference
			query = `INSERT INTO "UserPreference" ("userId") VALUES ($1)` // Include id
			_, err = db.DB.Exec(ctx, query, userID)                       // use Exec since we already have the id
			if err != nil {
				http.Error(w, "Database error", http.StatusInternalServerError)
				log.Println(err)
				return
			}
		} else {
			http.Error(w, "Database error", http.StatusInternalServerError)
			log.Println(err)
			return
		}

		// Save user ID in the gothic session (as gothic requires).
		err = gothic.StoreInSession("user_id", userID, r, w)
		if err != nil {
			http.Error(w, "Failed to save session", http.StatusInternalServerError)
			log.Println(err)
			return
		}

		// Save user data in the custom session store.
		session, _ := utils.SessionStore.Get(r, "user_session")
		session.Values["user_id"] = userID
		session.Save(r, w)

		var redirectSecure string
		if utils.IsProd {
			redirectSecure = os.Getenv("REDIRECT_SECURE_PROD")
		} else {
			redirectSecure = os.Getenv("REDIRECT_SECURE")
		}

		// Redirect to home page
		if redirectSecure == "" {
			redirectSecure = "http://localhost:3000"
		}

		http.Redirect(w, r, redirectSecure, http.StatusFound)
	}
}

in main i initialze a session:

var SessionStore *sessions.CookieStore

func init() {
	// Initialize Zerolog with pretty console output
	logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Timestamp().Logger()
	log.SetOutput(logger)

	// Load environment variables
	if err := godotenv.Load(); err != nil {
		LogInfoWithPrettyError("init", "No .env file found, using system environment variables", err)
	}
	// Load environment variables (moved to main)
	isProduction := os.Getenv("ENV") == "production"
	LogInfoWithPrettyError("init", fmt.Sprintf("is production: %v", isProduction), nil)

	sessionSecret := os.Getenv("SESSION_SECRET")
	if sessionSecret == "" {
		LogWarnWithPrettyError("init", "SESSION_SECRET environment variable not set, using default (not recommended for production)", nil)
		sessionSecret = "default-session-secret"
	}
	LogDebugWithPrettyError("init", "Session key loaded", nil)

	var domain string
	if isProduction {
		domain = os.Getenv("SESSION_COOKIE_DOMAIN_PROD")
	} else {
		domain = os.Getenv("SESSION_COOKIE_DOMAIN")
	}
	LogDebugWithPrettyError("init", fmt.Sprintf("Session cookie domain: %s", domain), nil)

	SessionStore = sessions.NewCookieStore([]byte(sessionSecret))
	SessionStore.Options = &sessions.Options{
		HttpOnly: true,
		Secure:   isProduction,
		Path:     "/",
		MaxAge:   86400 * 30,
		Domain:   domain, // Ensure this is set in .env
		SameSite: http.SameSiteNoneMode,
	}
	LogDebugWithPrettyError("init", "Session store initialized", nil)

	gothic.Store = SessionStore // Tell gothic to use our session store
	LogDebugWithPrettyError("init", "Gothic store set", nil)

	clientID := os.Getenv("GOOGLE_CLIENT_ID")
	clientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")

	var callbackURL string
	if isProduction {
		callbackURL = os.Getenv("GOOGLE_CALLBACK_URL_PROD")
	} else {
		callbackURL = os.Getenv("GOOGLE_CALLBACK_URL")
	}
	LogDebugWithPrettyError("init", fmt.Sprintf("Google Client ID: %s", clientID), nil)
	LogDebugWithPrettyError("init", "Google Client Secret loaded", nil)
	LogDebugWithPrettyError("init", fmt.Sprintf("Google Callback URL: %s", callbackURL), nil)

	if clientID == "" || clientSecret == "" || callbackURL == "" {
		LogWarnWithPrettyError("init", "Error: Google OAuth environment variables are not fully set in .env", nil)
	}

	goth.UseProviders(
		google.New(clientID, clientSecret, callbackURL, "email", "profile"),
	)
	LogDebugWithPrettyError("init", "Google OAuth provider initialized", nil)
}

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue provides no repository file or test path; start by locating GoogleCallbackHandler and the init function that configures SessionStore. Compare their behavior and production environment values under HTTPS, then verify the response headers and session behavior. Done means identifying a reproducible production cause and confirming the cookie works, but the issue does not define a repository-specific test.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, sql
Domain
authentication, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
18/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.