markbates / goth

Package goth provides a simple, clean, and idiomatic way to write authentication packages for Go web applications.
https://blog.gobuffalo.io/goth-needs-a-new-maintainer-626cd47ca37b
MIT License
5.2k stars 566 forks source link

You must select a provider (gin router issue) #554

Closed yanlkm closed 1 month ago

yanlkm commented 1 month ago

Hello, I'm trying to use the goth oauth for github authentification, Im using Gin Router. Whatever I'm trying to do, I got the same error of provider missing, Can someone help me ? Thank you

func ConfigureProviders() {

    key := os.Getenv("GITHUB_CLIENT_ID")
    secret := os.Getenv("GITHUB_CLIENT_SECRET")
    callbackURL := os.Getenv("BASE_URL") + "/auth/github/callback"

    // Initialize GitHub provider
    goth.UseProviders(
        github.New(key, secret, callbackURL),
    )
}
func GithubAuthHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        // Store the session in the cookie
        store := sessions.NewCookieStore([]byte("secret"))
        gothic.Store = store

        // debug
        fmt.Println("Store:", gothic.Store)
        // debug
        fmt.Println("Providers:", goth.GetProviders())

        // Start authentication
        gothic.BeginAuthHandler(c.Writer, c.Request)

        // debug
        fmt.Println("Authentification avec GitHub")
    }
}
func GithubAuthCallbackHandler(userService user.UserService) gin.HandlerFunc {
    return func(c *gin.Context) {
        // Continue with GitHub authentication processing
        gothUser, err := gothic.CompleteUserAuth(c.Writer, c.Request)
        if err != nil {
            // Handle the error here, for example, display an error message to the user
            fmt.Println("Erreur: ", err)
            return
        }

        // debug
        fmt.Println("gothUser:", gothUser)
        // Check if the user already exists in the database
        existingUser, err := userService.GetUserByUsername(c.Request.Context(), gothUser.Name)
        if err != nil {
            // Handle the error of user lookup
            return
        }

        if existingUser != nil {
            // The user already exists, log them in
            // Generate the JWT token
            objectID, err := primitive.ObjectIDFromHex(existingUser.ID)
            if err != nil {
                c.JSON(http.StatusBadRequest, gin.H{"error": "Impossible d'obtenir l'utilisateur"})
                return
            }
            token, err := utils.GenerateToken(&existingUser.Username, &objectID)
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de générer le jeton"})
                return
            }
            // Set the token in the cookie
            c.SetCookie("token", token, 72*3600, "/", "localhost", false, true)
            // Set the token in the header
            c.Header("Authorization", token)

            // Send the token in response
            c.JSON(http.StatusOK, gin.H{"status": true, "token": token, "message": "Vous êtes connecté avec GitHub!"})
        } else {
            // The user does not exist yet, create them
            newUser := user.User{
                Username:  gothUser.Name,
                Email:     gothUser.Email,
                CreatedAt: time.Now(),
                UpdatedAt: time.Now(),
            }

            err := userService.CreateUser(c.Request.Context(), &newUser)
            if err != nil {
                // Handle the error of user creation
                return
            }

            // User created, log them in
            // Generate the JWT token
            objectID, err := primitive.ObjectIDFromHex(newUser.ID)
            if err != nil {
                c.JSON(http.StatusBadRequest, gin.H{"error": "Impossible d'obtenir l'utilisateur"})
                return
            }
            token, err := utils.GenerateToken(&newUser.Username, &objectID)
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de générer le jeton"})
                return
            }
            // Set the token in the cookie
            c.SetCookie("token", token, 72*3600, "/", "localhost", false, true)
            // Set the token in the header
            c.Header("Authorization", token)

            // Send the token in response
            c.JSON(http.StatusOK, gin.H{"status": true, "token": token, "message": "Vous êtes connecté avec GitHub!"})
        }
    }
}

Here is my router code :

// Initialize the goth providers
    oauth.ConfigureProviders()

    r.GET("auth/github",
        oauth.GithubAuthHandler())

    r.GET("auth/github/callback",
        oauth.GithubAuthCallbackHandler(userService))

    // TODO: Delete favicon route
    r.StaticFile("/favicon.ico", "./favicon.ico")
harendra21 commented 1 month ago

Hi @yanlkm, I am using init(), and it is working for me, Here is code sample

func init() {
    goth.UseProviders(google.New("xxxxxxx", "xxxxxxx", "http://localhost:8080/v1/api/auth/google/callback", "email", "profile"))
}

func BeginGoogleAuth(c *gin.Context) {
    q := c.Request.URL.Query()
    q.Add("provider", "google")
    c.Request.URL.RawQuery = q.Encode()
    gothic.BeginAuthHandler(c.Writer, c.Request)
}
tmstorm commented 1 month ago

You can also create a function to add the provider to the context like this.

func addProviderToContext(ctx *gin.Context, provider string) *http.Request {
    return ctx.Request.WithContext(context.WithValue(ctx.Request.Context(), "provider", provider))
}

Then use it on callback like so.

ctx.Request = addProviderToContext(ctx, prov)
techknowlogick commented 1 month ago

Yes, the above suggestions are what I use too.