-2

I'm working on the REST API for a todolist app (no not from a tutorial) and i have successfully implemented authentication but one of my helper functions seems to be unable to read cookies that are clearly there, here is the function:

// jwt is imported from the https://github.com/dgrijalva/jwt-go package
func validateAccessToken(w http.ResponseWriter, r *http.Request) uuid.UUID {
    jwtSecret, exists := os.LookupEnv("JWT_SECRET")
    if !exists {
        w.WriteHeader(500)
        w.Write([]byte(`{"message":{"error":"Fatal internal error occurred"}}`))
        panic(errors.New("JWT_SECRET environment variable not set"))
    }
    // Get access token and then validate it
    at, err := r.Cookie("access_token")
    if err == http.ErrNoCookie {
        w.WriteHeader(401)
        w.Write([]byte(`{"message":{"error":"access_token cookie not found"}}`)) // this error is always returned when i attempt to use the handler that uses this function
        return uuid.Nil
    }
    t := at.Value
    token, err := jwt.ParseWithClaims(t, &models.UserClaims{}, func(token *jwt.Token) (interface{}, error) {
        return []byte(jwtSecret), nil
    })

    if claims, ok := token.Claims.(*models.UserClaims); ok && token.Valid {
        return claims.ID
    }
    w.WriteHeader(401)
    w.Write([]byte(`{"message":{"error":"access_token invalid"}}`))
    return uuid.Nil
}

and here is the relevant part of the code that sets the cookie:

// Login handles the login route
func Login(w http.ResponseWriter, r *http.Request) {
    //...
    // Construct cookies and then set them
    rtCookie := http.Cookie{
        Name:    "refresh_token",
        Value:   *rt,
        Expires: time.Now().Add(time.Nanosecond * time.Duration(sessionLifeNanos)),
    }
    atCookie := http.Cookie{
        Name:    "access_token",
        Value:   *at,
        Expires: time.Now().Add(time.Minute * 15),
    }
    http.SetCookie(w, &rtCookie)
    http.SetCookie(w, &atCookie)
    w.Write([]byte(`{"message":"Logged in successfully :)"}`))
}

and here is where validateAccessToken() is used and where it fails (uuid is the "github.com/google/uuid" package):

func CreateList(w http.ResponseWriter, r *http.Request) {
    // li will be used to store the decoded request body
    var li models.ListInput
    // listID will be used to store the returned id after inserting
    var listID uuid.UUID
    userID := validateAccessToken(w, r)
    fmt.Println(userID.String())
    if userID == uuid.Nil {
        return
    }
    //...
}

also, whenever i check after using the login route in postman, all of the cookies are sent and in the cookie jar (and no the "access_token" cookie is not expired) and also have correct looking values. I am perplexed as to why the validateAccessToken() function can't find a cookie that is there, also here is the serve() function which is called in main():

func serve() {
    // Initialise new router
    r := chi.NewRouter()
    // Some recommended middlewares
    r.Use(middleware.RequestID)
    r.Use(middleware.RealIP)
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    // Cors options
    r.Use(cors.Handler(cors.Options{
        AllowedOrigins:   []string{"*"},
        AllowedHeaders:   []string{"*"},
        AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
        ExposedHeaders:   []string{"Content-Type", "Set-Cookie", "Cookie"},
        AllowCredentials: true,
        MaxAge:           300,
    }))
    // API routes
    r.Route("/api", func(r chi.Router) {
        r.Route("/users", func(r chi.Router) {
            r.Post("/", handlers.CreateUser)
            r.Post("/login", handlers.Login)
        })
        r.Route("/lists", func(r chi.Router) {
            r.Post("/", handlers.CreateList)
        })
    })
    // Listen on port 5000 and log any errors
    log.Fatal(http.ListenAndServe("0.0.0.0:5000", r))
}

I very much appreciate any attempt to help and also i apologise for this badly put together question, I'm sort of in a rush to finish this.

2 Answers2

2

The application implicitly sets the cookie path to the login handler path. Fix by explicitly setting the cookie path to "/".

rtCookie := http.Cookie{
    Name:    "refresh_token",
    Path:    "/", // <--- add this line
    Value:   *rt,
    Expires: time.Now().Add(time.Nanosecond * time.Duration(sessionLifeNanos)),
}
atCookie := http.Cookie{
    Name:    "access_token",
    Path:    "/", // <--- add this line.
    Value:   *at,
    Expires: time.Now().Add(time.Minute * 15),
}
Charlie Tumahai
  • 113,709
  • 12
  • 249
  • 242
0

I'm not sure, but I think the problem may appear because of SameSite cookie policy. If you are testing the program from the frontend running on different port using modern browser versions, it may be that browser does not send cookies as they don't have the SameSite attribute.

Try to change your code to:

rtCookie := http.Cookie{
        Name:    "refresh_token",
        Value:   *rt,
        SameSite: http.SameSiteNoneMode,
        Expires: time.Now().Add(time.Nanosecond * time.Duration(sessionLifeNanos)),

    }
Ivan Novikov
  • 89
  • 1
  • 1