Autenticación usando JWT con Golang [Fiber] 2023 🧬

11 de Abril del 2023 ¿Ves algún error? Corregir artículo golang-wallpaper

La autenticación puede ser un dolor de cabeza cuando recién se esta empezando en el mundo de la programación por ello quiero hacerles la vida un poco más fácil y enseñarles a implementarla de manera básica usando el paquete JWT de Fiber en su versión 3 .

Preparemos el entorno:

Creamos nuestra carpeta 📁 donde alojaremos nuestro proyecto y inicializaremos nuestro proyecto de go usando el comando:

~
go mod init github.com/solrac97gr/basic-jwt-auth

No olvides remplazar solrac97gr por tu usuario de github.

Ahora necesitamos descargar 3 paquetes que usaremos en esta guía básica:

El paquete de JWT de Fiber

~
go get github.com/gofiber/jwt/v3

El paquete de JWT de Golang

~
go get github.com/golang-jwt/jwt/v4

Y por último el paquete de Fiber

~
go get github.com/gofiber/fiber/v2

Manos al código

La clave para ser un buen programador es mantenerse en constante aprendizaje. 👉🏽

Models:

Empezaremos por crear nuestras estructuras dentro de la carpeta models/models.go , puedes crearlo en archivos separados o juntos, para el ejemplo los pondré en uno solo pero en el repositorio estarán separados:

models/models.go
package models type LoginRequest struct { Email string `json:"email"` Password string `json:"password"` } type LoginResponse struct { Token string `json:"token"` } type User struct { ID int Email string Password string FavoritePhrase string }

Config

Dentro de la carpeta config/config.go tienes como tarea extraer la firma para tu token de las variables de entorno o de algún archivo de configuración, para el ejemplo usaré una constante esto nunca lo deberías hacer ya que comprometerías la seguridad del Token.

config/config.go
package config // The secret key used to sign the JWT, this must be a secure key and should not be stored in the code const Secret = "secret"

Middlewares

Los middlewares son herramientas dentro de la programación que nos pueden servir como preludio a acciones realizadas por nuestra API como por ejemplo validar que el usuario esta logeado o restringir el acceso a ciertos países.

Fiber nos provee con un middleware de JWT que es el que inicializaremos en la carpeta middlewares/auth.go:

middlewares/auth.go
package middlewares import ( "github.com/gofiber/fiber/v2" jwtware "github.com/gofiber/jwt/v3" ) // Middleware JWT function func NewAuthMiddleware(secret string) fiber.Handler { return jwtware.New(jwtware.Config{ SigningKey: []byte(secret), }) }

Repository

Para no sobre complicar el proyecto y desviarnos del foco usaremos una función que simulará una llamada a una base de datos y nos retornara el usuario cuando la contraseña y email coincidan o un error cuando no, esto estará en la carpeta repository/FindByCredentials.go .

repository/FindByCredentials.go
package repository import ( "errors" "github.com/solrac97gr/basic-jwt-auth/models" ) // Simulate a database call func FindByCredentials(email, password string) (*models.User, error) { // Here you would query your database for the user with the given email if email == "test@mail.com" && password == "test12345" { return &models.User{ ID: 1, Email: "test@mail.com", Password: "test12345", FavoritePhrase: "Hello, World!", }, nil } return nil, errors.New("user not found") }

Handlers

Ahora la parte importante, asumamos que nuestro usuario acaba de registrarse usando las siguientes credenciales.

~
{ "email": "test@mail.com", "password": "test12345" }

Crearemos el endpoint para que haga login y un endpoint protegido donde extraigamos la información almacenada en el token.

Esto lo haremos en la carpeta handlers/handlers.go.

repository/FindByCredentials.go
package handlers import ( "time" "github.com/gofiber/fiber/v2" jtoken "github.com/golang-jwt/jwt/v4" "github.com/solrac97gr/basic-jwt-auth/config" "github.com/solrac97gr/basic-jwt-auth/models" "github.com/solrac97gr/basic-jwt-auth/repository" ) // Login route func Login(c *fiber.Ctx) error { // Extract the credentials from the request body loginRequest := new(models.LoginRequest) if err := c.BodyParser(loginRequest); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": err.Error(), }) } // Find the user by credentials user, err := repository.FindByCredentials(loginRequest.Email, loginRequest.Password) if err != nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": err.Error(), }) } day := time.Hour * 24 // Create the JWT claims, which includes the user ID and expiry time claims := jtoken.MapClaims{ "ID": user.ID, "email": user.Email, "fav": user.FavoritePhrase, "exp": time.Now().Add(day * 1).Unix(), } // Create token token := jtoken.NewWithClaims(jtoken.SigningMethodHS256, claims) // Generate encoded token and send it as response. t, err := token.SignedString([]byte(config.Secret)) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": err.Error(), }) } // Return the token return c.JSON(models.LoginResponse{ Token: t, }) } // Protected route func Protected(c *fiber.Ctx) error { // Get the user from the context and return it user := c.Locals("user").(*jtoken.Token) claims := user.Claims.(jtoken.MapClaims) email := claims["email"].(string) favPhrase := claims["fav"].(string) return c.SendString("Welcome 👋" + email + " " + favPhrase) }

Momento de unir todo

Ahora en nuestro archivo main.go ubicado en la raíz del proyecto uniremos todas las partes y pondremos nuestra API en marcha.

main.go
package main import ( "github.com/gofiber/fiber/v2" "github.com/solrac97gr/basic-jwt-auth/config" "github.com/solrac97gr/basic-jwt-auth/handlers" "github.com/solrac97gr/basic-jwt-auth/middlewares" ) func main() { // Create a new Fiber instance app := fiber.New() // Create a new JWT middleware // Note: This is just an example, please use a secure secret key jwt := middlewares.NewAuthMiddleware(config.Secret) // Create a Login route app.Post("/login", handlers.Login) // Create a protected route app.Get("/protected", jwt, handlers.Protected) // Listen on port 3000 app.Listen(":3000") }

Ejecutando nuestra aplicación

Para ello una vez terminamos de crear los archivos necesarios ejecutaremos el siguiente comando.

~
go run main.go

Esto nos dará como resultado lo siguiente en la consola, eso significa que nuestra app ya esta corriendo en el puerto 3000

go-jwt-auth-login

Ahora probaremos usando postman para ejecutar nuestras peticiones.

Ejecutamos el Login y vemos que obtenemos nuestro Token 🌟

go-jwt-auth-login

Ahora usaremos nuestro token en la siguiente petición a la ruta protegida y listo nuestra ruta identifica que usuario esta logeado gracias a la información del token.

go-jwt-auth-protected

Puedes descargar las peticiones de Postman aquí

Así quedaría nuestro proyecto final

Pueden revisar el repositorio en el siguiente enlace github.com/basic-jwt-auth

Esta sería la estructura de archivos.

config

config.go

handlers

handlers.go

middlewares

auth.go

models

models.go

repository

FindByCredentials.go

go.mod

main.go

Cierre

De esta forma podemos autenticar usuarios usando JWT en Go Fiber, sin embargo la estructura de este proyecto tiene muchos puntos de mejora que abordo en este artículo y que puedes revisar para hacer algo más robusto usando arquitectura hexagonal y DDD.

Conviértete en un Go Ninja 🥷.Suscríbete a mi newsletter y recibe las últimas novedades en Go.