How to implement JWT Authetication in Go II
In the previous?blog, we learned how to set up the project, including connecting to the database and migrating the user model. We also covered the creation and validation of JWT tokens, as well as the implementation of JWT middleware. In this second part of the series, we will explore how to store JWT in cookies using Go, and we will also discuss the authentication routes.
Storing JWT in cookies
In the previous section, we learned how to use JWT authentication for our Go application. In this section, we will learn how to store JWT tokens in cookies for authentication.
Understanding HTTP cookies
An HTTP cookie is a small piece of data stored on the client’s computer by the web browser. The data is sent back to the server on every request to the server that the browser makes. Cookies are used to store user information or preferences for future use, and they are an essential part of web development.
Setting and retrieving cookies in Go
To set cookies in Go, we can use the SetCookie function provided by the net/http package. We can use this function to set the cookie's name, value, expiration time, and other attributes.
To retrieve cookies in Go, we can use the c.Cookie function provided by the gin context. This function returns a pointer to an http.Cookie object, which contains the cookie's value and attributes.
We will introduce some helper functions that will help us to set and clear the cookies. Here’s what the cookieHelper.go file looks like:
cookieHelper.go file looks like:
func SetCookie(c *gin.Context, name string, value string, expiration time.Time) {
cookie := buildCookie(name, value, expiration.Second())
http.SetCookie(c.Writer, cookie)
}
func ClearCookie(c *gin.Context, name string) {
cookie := buildCookie(name, "", -1)
http.SetCookie(c.Writer, cookie)
}
func buildCookie(name string, value string, expires int) *http.Cookie {
cookie := &http.Cookie{
Name: name,
Value: value,
Path: "/",
HttpOnly: true,
MaxAge: expires,
}
return cookie
}
In this example, we have buildCookie function that takes in the cookie's name, its value and its expiry time and returns an HTTP.Cookie. The SetCookie function is a helper function that sets an HTTP cookie for a given gin.Context. The function takes three arguments:
The function first calls the buildCookie function, passing in the name, value, and expiration arguments, and gets back a pointer to an http.Cookie instance. It then calls the http.SetCookie function to set the cookie on the response writer of the gin.Context.
The ClearCookie function is used to clear the value of an existing cookie by setting its value to an empty string and its MaxAge attribute to -1, indicating that the cookie has expired.
The function takes in two parameters:
The function calls the buildCookie helper function to create a new cookie object with the given name and an empty value and sets its MaxAge attribute to -1. The cookie is then written to the response header using the http.SetCookie function, which takes the c.Writer field of the gin.Context object as its first argument. This causes the client's browser to delete the specified cookie.
领英推荐
Routes
In this section, we will learn about the routes that we came across in the first part of the series. Let us have a look at the AuthRoutes route:
// authRouter.go
func AuthRoutes(incomingRoutes *gin.Engine) {
incomingRoutes.POST("/users/signup", controllers.Signup())
incomingRoutes.POST("/users/login", controllers.Login())
incomingRoutes.GET("/users/refresh-token", controllers.RefreshToken())
incomingRoutes.POST("/users/logout", controllers.Logout())
}
This code block defines the routes for the authentication functionality of the application. It utilizes the Gin web framework to create and manage the routes. The AuthRoutes function accepts an incomingRoutes parameter of the type *gin.Engine which is an instance of the Gin engine.
The POST method is used for the /users/signup and /users/login routes, which are responsible for creating a new user and logging in an existing user respectively. These routes are handled by the Signup and Login functions in the controllers package.
The /users/refresh-token route is a GET route that is responsible for generating a new access token and refresh token for an authenticated user. This route is handled by the RefreshToken function in the controllers package.
The /users/logout route is a POST route that is responsible for logging out an authenticated user by invalidating their refresh token. This route is handled by the Logout function in the controllers package.
By defining the routes in this way, the application can easily handle requests for user authentication and respond with the appropriate actions, such as creating a new user, logging in an existing user, refreshing access and refresh tokens, and logging out a user.
Now let’s have a look at the authController.go file. This file is a set of controller functions for handling user authentication and authorization in our web application.
The file starts by importing necessary packages and dependencies, including the Gin web framework, the Go validator package for data validation, and the bcrypt library for hashing passwords.
func UserResponse(user models.User) serializers.UserSerializer {
return serializers.UserSerializer{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
}
The UserResponse function returns a serialized user object with selected properties from the models.User struct.
Next, the Signup function handles user signup by first binding the request data to a models.User struct, validating the input using the validator package, and checking if a user already exists with the same email address. If the email is not already registered, the function hashes the password and creates a new user record in the database. It then generates an access token and a refresh token using the helpers.GenerateToken function and sets cookies with the tokens using the helpers.SetCookie function. Finally, the function returns a serialized user object in JSON format.
func Signup() gin.HandlerFunc {
return func(c *gin.Context) {
var existing_user models.User
var user models.User
if err := c.Bind(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := validate.Struct(user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
database.Database.Db.Find(&existing_user, "email = ?", user.Email)
if existing_user.ID != 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "User already exists with this email address"})
return
}
password, _ := hashPassword(string(user.Password))
user.Password = password
database.Database.Db.Create(&user)
accessToken, err := helpers.GenerateToken(&user, "access")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
refreshToken, err := helpers.GenerateToken(&user, "refresh")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
responseUser := UserResponse(user)
helpers.SetCookie(c, "jwt", accessToken, time.Now().Add(time.Hour*1))
helpers.SetCookie(c, "refresh_token", refreshToken, time.Now().Add(time.Hour*24*7))
c.JSON(http.StatusCreated, responseUser)
}
}
The Login function handles user login by first binding the request data to a map of strings and then checking if a user with the provided email exists in the database. If a user exists, the function uses the checkPasswordHash function to compare the provided password with the stored hashed password. If the passwords match, the function generates new access and refresh tokens, sets cookies with the tokens, and returns a serialized user object in JSON format.
Continue here