Search code examples
gopostmangorilla

Golang - How can I get authorization from mux?


I'm a new beginner of Golang, I start learning Gorilla/Mux with JWT.

I'm just success in doing register a user to MongoDB and then login. I use Postman to test. But when I try to browse to protected route. I get the error "Missing auth token" as I have in the verification.

How can postman get the authorization to browse protected route?

Below is the code of register and login:

Router Function

func Router() *mux.Router {

    router := mux.NewRouter().StrictSlash(true)
  router.Use(CommonMiddleware)

  //router.HandleFunc("/", middleware.TestAPI).Methods("GET", "OPTIONS")
  router.HandleFunc("/register", middleware.RegisterHandler).Methods("POST", "OPTIONS")
  router.HandleFunc("/login", middleware.LoginHandler).Methods("POST", "OPTIONS")

  secure := router.PathPrefix("/auth").Subrouter()
  secure.Use(auth.JwtVerify)

    secure.HandleFunc("/api/task", middleware.GetAllTask).Methods("GET", "OPTIONS")
    secure.HandleFunc("/api/task", middleware.CreateTask).Methods("POST", "OPTIONS")
    secure.HandleFunc("/api/task/{id}", middleware.TaskComplete).Methods("PUT", "OPTIONS")
    secure.HandleFunc("/api/undoTask/{id}", middleware.UndoTask).Methods("PUT", "OPTIONS")
    secure.HandleFunc("/api/deleteTask/{id}", middleware.DeleteTask).Methods("DELETE", "OPTIONS")
    secure.HandleFunc("/api/deleteAllTask", middleware.DeleteAllTask).Methods("DELETE", "OPTIONS")
    return router
}

func CommonMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Header().Add("Content-Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Access-Control-Request-Headers, Access-Control-Request-Method, Connection, Host, Origin, User-Agent, Referer, Cache-Control, X-header")
    next.ServeHTTP(w, r)
  })
}

Register function

func RegisterHandler(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  var user models.User
  body, _ := ioutil.ReadAll(r.Body)
  err := json.Unmarshal(body, &user)
  var res models.ResponseResult
  if err != nil {
    res.Error = err.Error()
    json.NewEncoder(w).Encode(res)
    return
  }

  var result models.User
  err = usercollection.FindOne(context.TODO(), bson.D{{"username", user.Username}}).Decode(&result)

  if err != nil {
    if err.Error() == "mongo: no documents in result" {
      hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 5)

      if err != nil {
        res.Error = "Error While Hashing Password, Try Again"
        json.NewEncoder(w).Encode(res)
        return
      }
      user.Password = string(hash)

      _, err = usercollection.InsertOne(context.TODO(), user)
      if err != nil {
        res.Error = "Error While Creating User, Try Again"
        json.NewEncoder(w).Encode(res)
        return
      }
      res.Result = "Register Successful"
      json.NewEncoder(w).Encode(res)
      return
    }
    res.Error = err.Error()
    json.NewEncoder(w).Encode(res)
    return
  }
  res.Result = "Username already exists!!"
  json.NewEncoder(w).Encode(res)
  return
}

Login Function

func LoginHandler(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  var user models.User
  body, _ := ioutil.ReadAll(r.Body)
  err := json.Unmarshal(body, &user)

  if err != nil {
    log.Fatal(err)
  }
  //var resultToken models.Token
  var resultUser models.User
  var res models.ResponseResult

  err = usercollection.FindOne(context.TODO(), bson.D{{"username", user.Username}}).Decode(&resultUser)

  if err != nil {
    res.Error = "Invalid username"
    json.NewEncoder(w).Encode(res)
    return
  }

  expiresAt := time.Now().Add(time.Minute * 100000).Unix()

  errf := bcrypt.CompareHashAndPassword([]byte(resultUser.Password), []byte(user.Password))

  if errf != nil && errf == bcrypt.ErrMismatchedHashAndPassword {
    var  res = map[string]interface{}{"status": false, "message": "Invalid login credential. Please try again"}
    json.NewEncoder(w).Encode(res)
    return
  }
  tk := &models.Token{
    Username: user.Username,
    StandardClaims: &jwt.StandardClaims{
      ExpiresAt: expiresAt,
    },
  }

  token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk)

  tokenString, err := token.SignedString([]byte("secret"))

  if err != nil {
    res.Error = "Error while generating token, Try again"
    json.NewEncoder(w).Encode(res)
    return
  }
  var resp = map[string]interface{}{"status": false, "message": "logged in"}
  resp["token"] = tokenString
  resp["tk"] = tk

  json.NewEncoder(w).Encode(resp)
}

Verification Function

func JwtVerify(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    var header = r.Header.Get("x-access-token")

    json.NewEncoder(w).Encode(r)
    header = strings.TrimSpace(header)

    if header == "" {
      w.WriteHeader(http.StatusForbidden)
      json.NewEncoder(w).Encode(Exception{Message: "Missing auth token"})
      return
    }

    tk := &models.Token{}

    _, err := jwt.ParseWithClaims(header, tk, func(token *jwt.Token) (interface{}, error) {
      return []byte("secret"), nil
    })

    if err != nil {
      w.WriteHeader(http.StatusForbidden)
      json.NewEncoder(w).Encode(Exception{Message: err.Error()})
      return
    }

    ctx := context.WithValue(r.Context(), "user", tk)
    next.ServeHTTP(w, r.WithContext(ctx))
  })
}

Solution

  • Since you are checking for the token x-access-token in the request header, you need to add the same while sending the request. This can be easily done in Postman as shown below -

    Screenshot of Postman showing how to set header in http request

    The router I've used is -

    package main

    func router() *mux.Router {
        router := mux.NewRouter().StrictSlash(true)
        secure := router.PathPrefix("/auth").Subrouter()
        secure.Use(auth.JwtVerify)
        secure.HandleFunc("/api", middleware.ApiHandler).Methods("GET")
        return router
    }
    
    func main() {
        r := router()
        http.ListenAndServe(":8080", r)
    }
    

    The middleware I've used is -

    package auth

    func JwtVerify(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            var header = r.Header.Get("x-access-token")
    
            json.NewEncoder(w).Encode(r)
            header = strings.TrimSpace(header)
    
            if header == "" {
                w.WriteHeader(http.StatusForbidden)
                json.NewEncoder(w).Encode("Missing auth token")
                return
            } else {
                json.NewEncoder(w).Encode(fmt.Sprintf("Token found. Value %s", header))
            }
            next.ServeHTTP(w, r)
        })
    }
    

    and the handler is -

    package middleware

    func ApiHandler(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode("SUCCESS!")
        return
    }