Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for JWT Authentication in Go
Siddhesh Khandagale
Siddhesh Khandagale

Posted on • Edited on

     

JWT Authentication in Go

Introduction :

Hey there 👋, In this tutorial, we are going to learn about implementing JWT Authentication in Golang REST-APIs using Fiber Web Framework, PostgreSQL DB and GORM.

JWT :

JSON Web Token (JWT) is a JSON-based open standard (RFC 7519) for creating access tokens that assert some number of claims. For example, a server could generate a token that has the claim "logged in as admin" and provide that to a client. The client could then use that token to prove that it is logged in as admin. The tokens are signed by the server's key, so the client and server are both able to verify that the token is legitimate. This allows the server to trust the token and grants the client the permissions associated with that token.

JWTs are commonly used as a way to authenticate users. When a user logs in to a server, the server creates a JWT that contains information about the user and signs it using a secret key. The server then sends the token back to the client, and the client stores it for future use. When the client wants to access a protected route or resource, it sends the JWT along with the request. The server can then verify the token and grant access to the protected route or resource if the token is valid.

What are we building :

We are going to build a Web API for a User to login, Register, See active User and Logout.

Image description

Prerequisites💯 :

To continue with the tutorial, firstly you need to have Golang, Fiber and PostgreSQL installed. If you've not gone through the previous tutorials on the Fiber Web Framework series you can see them here :)

Installations :

Getting Started 🚀:

Let's get started by creating the main project directory jwt-auth-api by using the following command.

(🟥Be careful, sometimes I've done the explanation by commenting in the code)

mkdir jwt-auth-api //Creates a 'jwt-auth-api' directorycd jwt-auth-api //Change directory to 'jwt-auth-api'
Enter fullscreen modeExit fullscreen mode

Now initialize a mod file. (If you publish a module, this must be a path from which your module can be downloaded by Go tools. That would be your code's repository.)

go mod init <repository-name>
Enter fullscreen modeExit fullscreen mode

In my case repository name isgithub.com/Siddheshk02/jwt-auth-api .

To install the Fiber Framework run the following command :

go get -u github.com/gofiber/fiber/v2
Enter fullscreen modeExit fullscreen mode

To install the Gorm and to install the Gorm Postgres driver, run the following commands resp. :

go get -u gorm.io/gormgo get -u gorm.io/driver/postgres
Enter fullscreen modeExit fullscreen mode

Initializing 💻:

Let's set up our server by creating a new instance of Fiber. For this create a filemain.go and add the following code to it :

package mainimport (    "github.com/Siddheshk02/jwt-auth-api/routes" // importing the routes package     "github.com/gofiber/fiber/v2")func main() {    app := fiber.New()    routes.Setup(app) // A routes package/folder is created with 'Setup' function created.    app.Listen(":8000")}
Enter fullscreen modeExit fullscreen mode

In theroutes.go , all the endpoints are created for getting user, login, register and logout.

Routes :

Add the following code in theroutes.go file :

package routesimport (    "github.com/gofiber/fiber/v2")func Setup(app *fiber.App) {    api := app.Group("/user")    api.Get("/get-user", func(c *fiber.Ctx) error {        return c.SendString("Hello World!!")    })}
Enter fullscreen modeExit fullscreen mode

Now let's run and test the API for the GET endpoint.

Run go runmain.go .

For testing the API, I'm usingPOSTMAN you can use any tool.

Image description
Now, let's add the other endpoints i.e. for login, register and logout.

Theroutes.go will look like the following code :

package routesimport (    "github.com/Siddheshk02/jwt-auth-api/controllers" // importing the routes package     "github.com/gofiber/fiber/v2")func Setup(app *fiber.App) {    api := app.Group("/user")    api.Get("/get-user", controllers.User)    api.Post("/register", controllers.Register)    api.Post("/login", controllers.Login)    api.Post("/logout", controllers.Logout)}
Enter fullscreen modeExit fullscreen mode

The User, Register, Login and Logout are the functions we are going to create in the controller.go file in the controller package/folder. These are going to perform the actual task.

Firstly, let's try a simple task through the Register function, the following code is added to the Register function.

(For now, You can comment on the other API endpoints i.e./login ,/get-user and/logout )

package controllersimport "github.com/gofiber/fiber/v2"func Register(c *fiber.Ctx) error {    var data map[string]string    if err := c.BodyParser(&data); err != nil {        return err    }    return c.JSON(data)}
Enter fullscreen modeExit fullscreen mode

Now, test the/register endpoint by passing the data as shown in the image below.

If there is no error then the exact data will be printed.

Image description

Database :

Let's create the database and name it asjwt-auth-api , the steps to create the database are Database>>Create>>Database.

Image description
Once the database is created, now make a folder database in which we are going to makedbconn.go file. In this file, we are going to add the database connection and migration function.

package databaseimport (    "fmt"    "log"    "github.com/Siddheshk02/jwt-auth-api/models" // this will be imported after you've created the User Model in the models.go file    "gorm.io/driver/postgres"    "gorm.io/gorm")const (    host     = "localhost"    port     = 5432    user     = "postgres"    password = "<password>" //Enter your password for the DB    dbname   = "jwt-auth-api")var dsn string = fmt.Sprintf("host=%s port=%d user=%s "+    "password=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai",    host, port, user, password, dbname)var DB *gorm.DBfunc DBconn() {    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})    if err != nil {        log.Fatal(err)    }    DB = db    db.AutoMigrate(&models.User{}) // we are going to create a models.go file for the User Model.}
Enter fullscreen modeExit fullscreen mode

The functiongorm.Open() creates a new connection pool whenever it is called.

db.AutoMigrate() call helps in creating the table if it is not already present. Database migration is usually things that change the structure of the database over time and this helps in making sure that the database structure is properly migrated to the latest version.

Make a foldermodels in whichmodels.go will be created and updated file with the following code in it.

package modelsimport "gorm.io/gorm"type User struct {    gorm.Model    Name     string `json:"name"`    Email    string `json:"email" gorm:"unique"`    Password []byte `json:"-"`}
Enter fullscreen modeExit fullscreen mode

As you can see, our User Model will have a Name, Email, and Password. Here, the Email will be unique. This means, that once we complete our application and try to register new users with the same email, the code won’t allow us to do it. The best part is that you don’t have to write any code specifically for this. Everything is handled by GORM.

Thegorm.Model specification adds some default properties to the Model, like id, created date, modified date, and deleted date.

Now, Update themain.go file with the following code.

package mainimport (    "github.com/Siddheshk02/jwt-auth-api/database"    "github.com/Siddheshk02/jwt-auth-api/routes"    "github.com/gofiber/fiber/v2")func main() {    database.DBconn()    app := fiber.New()    routes.Setup(app)    app.Listen(":8000")}
Enter fullscreen modeExit fullscreen mode

Register :

Let's update theregister() function in thecontrollers.go file according to the User Model and database table we've created.

func Register(c *fiber.Ctx) error {    var data map[string]string    if err := c.BodyParser(&data); err != nil {        return err    }    password, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 14) //GenerateFromPassword returns the bcrypt hash of the password at the given cost i.e. (14 in our case).    user := models.User{        Name:     data["name"],        Email:    data["email"],        Password: password,    }    database.DB.Create(&user) //Adds the data to the DB    return c.JSON(user)}
Enter fullscreen modeExit fullscreen mode

Now, test the endpoint to store the information.

Image description

Login :

Let's make the Login work. Before this uncomment the route for the login in theroutes.go file

In the Login we will get the same data string as in Register and we will check the email entered with the database if it is present or not.

If it is present then we will compare the Passwords using an inbuilt function.

func Login(c *fiber.Ctx) error {    var data map[string]string    if err := c.BodyParser(&data); err != nil {        return err    }    var user models.User    database.DB.Where("email = ?", data["email"]).First(&user) //Check the email is present in the DB    if user.ID == 0 { //If the ID return is '0' then there is no such email present in the DB        c.Status(fiber.StatusNotFound)        return c.JSON(fiber.Map{            "message": "user not found",        })    }    if err := bcrypt.CompareHashAndPassword(user.Password, []byte(data["password"])); err != nil {        c.Status(fiber.StatusBadRequest)        return c.JSON(fiber.Map{            "message": "incorrect password",        })    } // If the email is present in the DB then compare the Passwords and if incorrect password then return error.    return c.JSON(user) // If Login is Successfully done return the User data.}
Enter fullscreen modeExit fullscreen mode

Test the Login endpoint :

Image description
Now we are successfully returning a user but we need to return a JWT Token.

For this, we need a package to be installed.

go get github.com/golang-jwt/jwt
Enter fullscreen modeExit fullscreen mode

Update theLogin() function wit the following code,

const SecretKey = "secret"func Login(c *fiber.Ctx) error {    var data map[string]string    if err := c.BodyParser(&data); err != nil {        return err    }    var user models.User    database.DB.Where("email = ?", data["email"]).First(&user) //Check the email is present in the DB    if user.ID == 0 { //If the ID return is '0' then there is no such email present in the DB        c.Status(fiber.StatusNotFound)        return c.JSON(fiber.Map{            "message": "user not found",        })    }    if err := bcrypt.CompareHashAndPassword(user.Password, []byte(data["password"])); err != nil {        c.Status(fiber.StatusBadRequest)        return c.JSON(fiber.Map{            "message": "incorrect password",        })    } // If the email is present in the DB then compare the Passwords and if incorrect password then return error.    claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{        Issuer:    strconv.Itoa(int(user.ID)), //issuer contains the ID of the user.        ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), //Adds time to the token i.e. 24 hours.    })    token, err := claims.SignedString([]byte(SecretKey))    if err != nil {        c.Status(fiber.StatusInternalServerError)        return c.JSON(fiber.Map{            "message": "could not login",        })    }    cookie := fiber.Cookie{        Name:     "jwt",        Value:    token,        Expires:  time.Now().Add(time.Hour * 24),        HTTPOnly: true,    } //Creates the cookie to be passed.    c.Cookie(&cookie)    return c.JSON(fiber.Map{        "message": "success",    })}
Enter fullscreen modeExit fullscreen mode

NewWithClaims takes two parameters, asigning method andclaims. Claims are the actual data that the JWT token will contain.
jwt.NewWithClaims doesn't create the new token, you need to call theSignedString function passing it the secret key to get the actual JWT token. We stored this token in a cookie.
Now, for cors issue i.e. (the problem which arises when the backend is running on a different port while the front-end is running on a different port), we are using the cors.New() function in themain.go file.
Update themain.go file, it will look like the following code

package mainimport (    "github.com/Siddheshk02/jwt-auth-api/database"    "github.com/Siddheshk02/jwt-auth-api/routes"    "github.com/gofiber/fiber/v2"    "github.com/gofiber/fiber/v2/middleware/cors")func main() {    database.DBconn()    app := fiber.New()    app.Use(cors.New(cors.Config{        AllowCredentials: true, //Very important while using a HTTPonly Cookie, frontend can easily get and return back the cookie.    }))    routes.Setup(app)    app.Listen(":8000")}
Enter fullscreen modeExit fullscreen mode

Now, let's test the login endpoint and see the response. Send the request and then click on theCookies(1) section.

Image description

User :

Before moving ahead, uncomment the Get endpoint for the get-user in theroutes.go file i.e.api.Get("/get-user", controllers.User) .

Now, let's update the functionUser() for getting the logged-in user by using the cookie.

func User(c *fiber.Ctx) error {    cookie := c.Cookies("jwt")    token, err := jwt.ParseWithClaims(cookie, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {        return []byte(SecretKey), nil //using the SecretKey which was generated in th Login function    })    if err != nil {        c.Status(fiber.StatusUnauthorized)        return c.JSON(fiber.Map{            "message": "unauthenticated",        })    }    claims := token.Claims.(*jwt.StandardClaims)    var user models.User    database.DB.Where("id = ?", claims.Issuer).First(&user)    return c.JSON(user)}
Enter fullscreen modeExit fullscreen mode

Thejwt.ParseWithClaims accepts the secret key, it takes a function as the 3rd argument in which you have to return the key.

Test the get-user endpoint.

Image description
Here, we can see the password. So, if you don't want to show the password, update themodels.go as,

//Previous : Password []byte `json:"password"`Password []byte `json:"-"`
Enter fullscreen modeExit fullscreen mode

Send a request again and you'll not get the password.

Logout :

Before moving ahead, uncomment the Post endpoint for the logout in theroutes.go file i.e.api.Post("/logout", controllers.Logout) .

Now, let's update the functionLogout() for logging out the present user.
For logging out the user, we need to delete the cookie, but there is no way to remove the cookie in the browser. So, we'll create a different cookie and set its expiry time in the past. Then we'll set the cookie and return the response.

func Logout(c *fiber.Ctx) error {    cookie := fiber.Cookie{        Name:     "jwt",        Value:    "",        Expires:  time.Now().Add(-time.Hour), //Sets the expiry time an hour ago in the past.        HTTPOnly: true,    }    c.Cookie(&cookie)    return c.JSON(fiber.Map{        "message": "success",    })}
Enter fullscreen modeExit fullscreen mode

Now, test the logout endpoint.

Image description
Now if you test the /get-user endpoint to see the logged-in user, you'll not be able to see any user.

Image description
So, there is no logged-in user. If we log-in again, then another cookie will be generated while retrieving the user.

So, this is how JWT Authentication works in golang.

The complete code is saved in thisGitHub repository.

Conclusion :

Image description
You've Successfully created a REST-API and secured it with JWT Authentication ✨💯

To get more information about Golang concepts, projects, etc. and to stay updated on the Tutorials do followSiddhesh on Twitter andGitHub.

Until then Keep Learning, Keep Building 🚀🚀

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Backend Developer specialized in Web Apps, CLI tools, and AI integration with Golang.
  • Pronouns
    He/Him
  • Work
    Freelance Developer
  • Joined

More fromSiddhesh Khandagale

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp