Authentication using JWT with Golang [Fiber] 2023 🧬

April 11th, 2023 ¿Ves algún error? Corregir artículo golang-wallpaper

Authentication can be a headache when you're just starting in the programming world, so I want to make your life a bit easier and teach you how to implement it in a basic way using the Fiber JWT package in version 3 .

Let's prepare the environment:

Create a folder 📁 where we'll host our project and initialize our Go project using the command:

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

Don't forget to replace solrac97gr with your GitHub username.

Now we need to download 3 packages that we'll use in this basic guide:

The Fiber JWT package

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

The Golang JWT package

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

And finally the Fiber package

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

Hands on code

Models:

We'll start by creating our structures inside the models/models.go folder. You can create them in separate files or together - for the example I'll put them in one but in the repository they'll be separated:

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

Inside the config/config.go folder, your task is to extract the signature for your token from environment variables or a configuration file. For the example I'll use a constant - you should never do this as it would compromise the Token's security.

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

Middlewares are tools in programming that can serve as a prelude to actions performed by our API, such as validating that the user is logged in or restricting access to certain countries.

Fiber provides us with a JWT middleware that we'll initialize in the middlewares/auth.go folder:

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

To not overcomplicate the project and deviate from the focus, we'll use a function that will simulate a database call and return the user when the password and email match, or an error when they don't. This will be in the repository/FindByCredentials.go folder.

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

Now the important part - let's assume our user just registered using the following credentials.

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

We'll create the endpoint for login and a protected endpoint where we extract the information stored in the token.

We'll do this in the handlers/handlers.go folder.

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) }

Time to bring it all together

Now in our main.go file located at the project root, we'll unite all the parts and get our API up and running.

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") }

Running our application

Once we finish creating the necessary files, we'll execute the following command.

~
go run main.go

This will give us the following result in the console, which means our app is already running on port 3000

go-jwt-auth-login

Now we'll test using postman to execute our requests.

We execute the Login and see that we get our Token 🌟

go-jwt-auth-login

Now we'll use our token in the next request to the protected route and that's it - our route identifies which user is logged in thanks to the token information.

go-jwt-auth-protected

You can download the Postman requests here

This is how our final project would look

You can review the repository at the following link github.com/basic-jwt-auth

This would be the file structure.

config

config.go

handlers

handlers.go

middlewares

auth.go

models

models.go

repository

FindByCredentials.go

go.mod

main.go

Conclusion

This is how we can authenticate users using JWT in Go Fiber. However, this project structure has many points for improvement that I address in this article and that you can review to build something more robust using hexagonal architecture and DDD.