No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Implementando el middleware

16/26
Recursos

Aportes 7

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

La solución que hace casi al final de la clase, la de modificar el fichero auth.go y enviar una referencia al models.AppClaims, también debe hacerse a la función de MeHandler

func MeHandler(s server.Server) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tokenString := strings.TrimSpace(r.Header.Get("Authorization"))

		token, err := jwt.ParseWithClaims(tokenString, &models.AppClaims{}, func(token *jwt.Token) (interface{}, error) {
			return []byte(s.Config().JWTSecret), nil
		})
		if err != nil {
			http.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}

		if claims, ok := token.Claims.(*models.AppClaims); ok && token.Valid {
			user, err := repository.GetUserById(r.Context(), claims.UserId)
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			w.Header().Set("Content-Type", "application/json")
			json.NewEncoder(w).Encode(user)
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

La interface de la librería debería contemplar este problema en forma de chequeo de tipos y, especificar, que se debe recibir una referencia estrictamente. De esta forma el compilador te avisaría sobre este error en lugar de recibirlo cuando pruebas la aplicación.

Para la abstracción de código he creado una carpeta llamada helpers y en ella un fichero llamado jwtHelper.go, el cual contiene la siguiente función:

func GetJWTAuthorizationInfo(s server.Server, w http.ResponseWriter, r *http.Request) (*jwt.Token, error) {
	tokenString := strings.TrimSpace(r.Header.Get("Authorization"))
	token, err := jwt.ParseWithClaims(tokenString, models.AppClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(s.Config().JWTSecret), nil
	})

	if err != nil {
		http.Error(w, err.Error(), http.StatusUnauthorized)
	}

	return token, err
}

Los códigos que realizan la llamada la pueden utilizar tal que así:

		token, err := helpers.GetJWTAuthorizationInfo(s, w, r)

		if err != nil {
			return
		}

Ya para no duplicar el código del middleware de autorización lo que hice fue extraer ahí mismo el userId del token e insertarlo en los headers.
Entonces cuando llega al MeHandler puedo extraer del los headers el userId.
No se si esté bien, me gustaría que alguien me diera su opinión
Este es el middleware

Este es el handler

Les comparto una opción para no repetir el código de verificación del token .
En el middleware, al verificar el token podemos obtener el UserId y lo guardamos en el contexto de ejecución; posteriormente en el handler podemos recuperarlo y traer la data del usuario.


middleware/auth.go

func CheckAuthMiddleware(s server.Server) func(h http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if !shouldCheckToken(r.URL.Host) {
				next.ServeHTTP(w, r)
				return
			}
			tokenString := strings.TrimSpace(r.Header.Get("Authorization"))
			token, err := jwt.ParseWithClaims(tokenString, &models.AppClaims{}, func(t *jwt.Token) (interface{}, error) {
				return []byte(s.Config().JWTSecret), nil
			})
			if err != nil {
				http.Error(w, err.Error(), http.StatusUnauthorized)
				return
			}

			claims, ok := token.Claims.(*models.AppClaims)
			if ok && token.Valid {
				log.Printf("User ID: %s", claims.UserId)
				ctx := context.WithValue(r.Context(), ContextUserID, claims.UserId)
				r = r.WithContext(ctx)
			}// 

			next.ServeHTTP(w, r)
		})
	}
}

handlers/user.go

func MeHandler(s server.Server) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		userID := r.Context().Value(middleware.ContextUserID)
		if userID == "" {
			http.Error(w, "Invalid credentials", http.StatusUnauthorized)
			return
		}

		user, err := repository.GetUserById(r.Context(), userID.(string))
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(user)
	}
}
func MeHandler(s server.Server) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tokenString := strings.TrimSpace(r.Header.Get("Authorization"))
		token, err := jwt.ParseWithClaims(tokenString, &models.AppClaims{}, func(token *jwt.Token) (interface{}, error) {
			return []byte(s.Config().JWTSecret), nil
		})
		if err != nil {
			http.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}
		if claims, ok := token.Claims.(*models.AppClaims); ok && token.Valid {
			user, err := repository.GetUserById(r.Context(), claims.UserId)
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			w.Header().Set("Content-Type", "application/json")
			json.NewEncoder(w).Encode(user)
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

Una posible solución para el reto que nos plantea Nestor es la siguiente:

func ParseToken(hauth string, claims jwt.Claims) (jwt.Token, error) {
	// hauth: Autohorization Header
	// claims: Any type that implemnts jwt.Claims. AppClaims Does b/c jwt.StandardClaims does.
	strim := strings.TrimSpace(hauth)
	token, err := jwt.ParseWithClaims(strim, claims, func(t *jwt.Token) (interface{}, error) {
		return []byte(database.BuildConfig().JWTSecret), nil
	})
	return *token, err
}

el database.BuildConfig está apuntando al archivo de configuración, que a propósito no estaría mal sacarlo de server.go y tenerlo en su propio archivo config.go., esto entre otras cosas ahorraría tener que pasar el servidor como parámetro en los los middlewares.