markbates / markbates/goth

Session returns empty array

Open
#567 2 comments 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

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:
```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:

```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:
```go
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)
}
}
```

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

Start with auth.go's NewAuth session-store setup and public.go's GetAuthCallbackFunction, GetAuthHandler, and GetAuthStatus entry points. Reproduce the Google callback flow and inspect the request used by /auth/status, then trace whether the same session is available after the redirect. Done means the callback establishes a readable session and /auth/status returns the authenticated user instead of an empty value.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.