Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Session returns empty array #567

Open
cantutar opened this issue Jul 2, 2024 · 2 comments
Open

Session returns empty array #567

cantutar opened this issue Jul 2, 2024 · 2 comments

Comments

@cantutar
Copy link

cantutar commented Jul 2, 2024

Hi,

Im having a hard time to figure out why package is not working as expected.(Maybe im using wrong lol) but my current expected behavior put google and make it work with vite-react spa app. currently it redirects succesfully but not sets any session or cannot read any session with it so im using go 1.22.4 with;

  • github.com/gin-gonic/gin v1.10.0,
  • github.com/gorilla/sessions v1.3.0
  • github.com/gin-gonic/gin v1.10.0

(im not sure what causes the issue but after fighting straight 3 days with the package to make it work with gin and not getting a result is kinda bummer, i added redirects with sending vales to front as a workaround but its no use after no session to check basically. :( )

Edit: currently using with dev mode

my service for auth.go:

const (
	key    = "secret"                               // TODO: change this to a more secure key
	MaxAge = int(time.Hour * 24 * 30 / time.Second) // This converts time.Duration to seconds.
)

var BaseUrl string

// var PORT string

func GetBaseURL() string {
	BaseUrl = os.Getenv("BACKEND_URL")
	// PORT = os.Getenv("PORT")
	// return fmt.Sprintf("%s:%s", BaseUrl, PORT)
	return BaseUrl
}

// buildAuthCallbackURL builds the callback URL for the authentication provider
// based on the provider name and remember to change the api version if it changes.
func buildAuthCallbackURL(provider string) string {
	baseUrl := GetBaseURL()
	url := fmt.Sprintf("%s/api/v1/auth/%s/callback", baseUrl, provider)
	fmt.Println(url)
	return url
}

func NewAuth() {
	googleClientID := os.Getenv("GOOGLE_CLIENT_ID")
	googleClientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")

	store := sessions.NewCookieStore([]byte(key))
	fmt.Println("MaxAge", MaxAge)
	store.MaxAge(MaxAge)

	store.Options.Path = "/"
	store.Options.HttpOnly = true
	store.Options.Secure = helpers.IsProduction()

	store.Options.SameSite = http.SameSiteLaxMode
	if helpers.IsProduction() {
		store.Options.SameSite = http.SameSiteStrictMode
	}

	gothic.Store = store

	goth.UseProviders(
		google.New(googleClientID, googleClientSecret, buildAuthCallbackURL("google")),
	)
}

public routes to handle callback etc:
public.go:

var (
	once         sync.Once
	websiteURL   string
	dashURL      string
	onboardURL   string
	errorPageURL string
)

// initializeURLs is used to initialize all URLs once.
func initializeURLs() {
	websiteURL = os.Getenv("WEB_APP_URL")
	if websiteURL == "" {
		log.Fatal("WEB_APP_URL is not set, terminating application.")
	}
	dashURL = fmt.Sprintf("%s/", websiteURL)
	onboardURL = fmt.Sprintf("%s/onboarding", websiteURL)
	errorPageURL = fmt.Sprintf("%s/auth-error", websiteURL)
}

// getDashboardURL returns the dashboard URL.
func getDashboardURL() string {
	once.Do(initializeURLs) // Initialize the URLs
	return dashURL
}

// getOnboardingURL returns the onboarding URL.
func getOnboardingURL() string {
	once.Do(initializeURLs) // Initialize the URLs
	return onboardURL
}

// getErrorPageURL returns the error page URL with an error message.
func getErrorPageURL(errorMessage string) string {
	once.Do(initializeURLs)                      // Initialize the URLs
	safeMessage := url.QueryEscape(errorMessage) // Protection against XSS attacks
	return fmt.Sprintf("%s?error=%s", errorPageURL, safeMessage)
}

type contextKey string

const providerKey contextKey = "provider"

func GetAuthCallbackFunction(c *gin.Context) {
	provider := c.Param("provider")
	if provider == "" {
		utils.RespondWithError(c, http.StatusBadRequest, "Provider not specified", nil)
		return
	}

	c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), providerKey, provider))

	// Complete user authentication using gothic package
	gothUser, err := gothic.CompleteUserAuth(c.Writer, c.Request)
	if err != nil {
		log.Printf("Authentication failed: %s", err.Error()) // Log the error
		// Show a general error message to the user
		errorMessage := "Authentication failed. Please try again or contact support if the problem persists."
		c.Redirect(http.StatusFound, getErrorPageURL(errorMessage))
		return
	}

	// Now using provider and social ID to check user existence
	userID, exists, err := services.CheckUserExistAndGetID(gothUser.Provider, gothUser.UserID)
	if err != nil {
		utils.RespondWithError(c, http.StatusInternalServerError, "Database operation failed", err)
		return
	}

	// Redirect based on whether the user exists
	var redirectURL string
	if exists {
		// Redirect URL for existing users
		redirectURL = fmt.Sprintf("%s?userID=%s&provider=%s", getDashboardURL(), userID.String(), gothUser.Provider)
	} else {
		// Redirect to the onboarding URL for new users
		redirectURL = fmt.Sprintf("%s?provider=%s&email=%s&picture=%s&socialID=%s",
			getOnboardingURL(), gothUser.Provider, url.QueryEscape(gothUser.Email), url.QueryEscape(gothUser.AvatarURL), gothUser.UserID)
	}

	c.Redirect(http.StatusFound, redirectURL)
}

func LogoutHandler(c *gin.Context) {
	provider := c.Param("provider")

	c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), providerKey, provider))

	err := gothic.Logout(c.Writer, c.Request)
	if err != nil {
		utils.RespondWithError(c, http.StatusInternalServerError, "Failed to log out", err)
		return
	}

	c.Redirect(http.StatusFound, getDashboardURL())
}

func GetAuthHandler(c *gin.Context) {
	provider := c.Param("provider")

	// Attempt to complete the user authentication from an existing session
	if gothUser, err := gothic.CompleteUserAuth(c.Writer, c.Request); err == nil {
		// User is already authenticated, redirect to home or another appropriate page
		c.JSON(http.StatusFound, gin.H{
			"user":        gothUser,
			"redirectURL": getDashboardURL(), // Suggest where to redirect
		})
		return
	}

	// No valid session, need to authenticate

	// Add 'provider' to the query parameters of the request
	q := c.Request.URL.Query()
	q.Add("provider", provider)
	c.Request.URL.RawQuery = q.Encode()

	// Set the modified request back to context with the provider key
	c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), providerKey, provider))

	// Start the authentication process since no existing session is found
	gothic.BeginAuthHandler(c.Writer, c.Request)
}

// GetAuthStatus checks the user's authentication status.
func GetAuthStatus(c *gin.Context) {
	// Check the user's session
	gothUser, err := gothic.CompleteUserAuth(c.Writer, c.Request)
	fmt.Println(gothUser) // this returns an empty map
	if err != nil {
		utils.RespondWithSuccess(c, gin.H{"isAuthenticated": false}, "User is not authenticated")
	} else {
		utils.RespondWithSuccess(c, gin.H{"isAuthenticated": true, "user": gothUser}, "User is authenticated")
	}
}

routes:

	authGroup := router.Group("/auth")
	{
		authGroup.GET("/status", routes.GetAuthStatus)

		authProviderGroup := authGroup.Group("/:provider")
		{
			authProviderGroup.GET("/callback", routes.GetAuthCallbackFunction)
			authProviderGroup.GET("/logout", routes.LogoutHandler)
			authProviderGroup.GET("/", routes.GetAuthHandler)
		}
	}
@Marin260
Copy link

@cantutar the latest version of gorilla sessions breaks this package.

I didn't really look into it to much, and I know this might not be the best solution but if you want a quick and dirty fix, downgrade the package version.

Related issue: Updating dependencies breaks login: 549

github.com/gorilla/sessions v1.2.2 // indirect

@cantutar
Copy link
Author

Thanks for the heads up, I was in hurry to finish the project for tight deadline so switched auth0, maybe later try it again. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants