gooauthbackendgo-fiber

How can I store an OAuth2 token and associate it with a user?


I'm very new to go and trying to make a backend that uses OAuth to login (I'm using golang.org/x/oauth2). Currently, I have my login route and my callback route that work perfectly fine. I can exchange my token and get my user info. Great. But I want to make protected routes. How can I store the token, and how can I access it? And more importantly, how can I infer the user's identifying keys (like their email) from the token? I looked up so many tutorials online, but unfortunately they all seem to stop at the stage I'm currently at and never get to the part where they implement protected routes.

Here's my callback route:

func Callback(c *fiber.Ctx) error {
    state := c.Query("state")
    if state != os.Getenv("STATE_KEY") {
        return c.SendString("States don't Match!!")
    }

    code := c.Query("code")

    googleConfig := auth.GoogleConfig()

    token, err := googleConfig.Exchange(context.Background(), code)
    if err != nil {
        return c.SendString("Code-Token Exchange Failed")
    }

    resp, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + token.AccessToken)
    if err != nil {
        return c.SendString("User Data Fetch Failed")
    }

    userDataBytes, err := io.ReadAll(resp.Body)
    if err != nil {
        return c.SendString("JSON Parsing Failed")
    }

    var userData UserData
    err = json.Unmarshal(userDataBytes, &userData)
    if err != nil {
        return c.SendString("JSON Parsing Failed")
    }
    var existingUser models.User
    if err := database.DB.Db.Where("email = ?", userData.Email).First(&existingUser).Error; err != nil {
        // User doesn't exist, create a new User instance
        newUser := models.User{
            Email:       userData.Email,
            Name:        userData.Name,
            AvatarURL:   userData.Picture,
            FirstName:   userData.GivenName,
            LastName:    userData.FamilyName,
            NickName:    userData.Name,
            DisplayName: userData.Name,
        }

        // Insert the new user into the database
        if err := database.DB.Db.Create(&newUser).Error; err != nil {
            return c.SendString("Error inserting user into the database")
        }
    }

    return c.JSON(existingUser)

}

Any help would be greatly appreciated!


Solution

  • Update: I implemented the rest as Peter specified above. Using this tutorial, I don't store the JWT token. I save the user to my database, and then use the following function to check if a user is logged in or not:

    func Authorize(c *fiber.Ctx) (*models.User, error) {
        cookie := c.Cookies("jwt")
    
        token, err := jwt.ParseWithClaims(cookie, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
            return []byte(os.Getenv("JWT_SECRET")), nil // I store the JWT secret as an environment variable
        })
    
        if err != nil {
            return nil, err
        }
    
        claims := token.Claims.(*jwt.StandardClaims)
    
        var user models.User
        err = database.DB.Db.Where("id = ?", claims.Issuer).First(&user).Error
        if err != nil {
            return nil, err
        }
    
        return &user, nil
    }
    

    Note: when making a fetch() request from the client, you need to include {credentials: "include"} as the second argument of the function, like so:

    fetch(apiUrl + "/api/test", {
        credentials: "include",
    });