Skip to content

cookie not issued on prod. #603

Description

@kaleb110

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)
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions