Tutorial: Building on Go Gin JWT Authentication – Part 2
Welcome to the second part of our Go Gin JWT Authentication tutorial series! If you haven’t checked out the first part, make sure to do so here. In this tutorial, we’ll expand our authentication system to include user login, registration, and a user profile page with updates. Additionally, we’ll introduce a flash messaging system to enhance user experience.
Prerequisites
Before you proceed, make sure you have completed the first part of the tutorial. If you haven’t, please refer to Part 1 for the initial setup and JWT authentication.
- Go installed on your machine
- MySQL database installed and running
- Basic understanding of Go Gin and JWT authentication
New Files
We’ve added several new files to our project to support user registration and profile updates:
mywebapp/
├── static/
│ ├── css/
│ └── js/
└── controllers/
│ └── AuthController.go
│ └── ProfileController.go
└── config/
│ └── mysql.go
└── middlewares/
│ └── AuthMiddleware.go
│ └── FlashMessageMiddleware.go
└── models/
└── views/
│ └── login.tmpl
│ └── profile.tmpl
│ └── register.tmpl
└── utils/
└── RenderTemplate.go
Step 1: Create register.tmpl
This is a simple registration page with username, and password required. Additionally, there’s a section to display flash messages.
<!-- views/register.tmpl -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register</title>
</head>
<body>
<h2>Register</h2>
<!-- Flash messages -->
{{ with .Flash }}
{{ range . }}
<div class="flash-message">{{ . }}</div>
{{ end }}
{{ end }}
<form action="/api/register" method="POST">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required><br>
<label for="username">Username:</label>
<input type="text" id="username" name="username" required><br>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required><br>
<button type="submit">Register</button>
</form>
<!-- Login link -->
<p>Already have an account? <a href="/login">Login here</a></p>
</body>
</html>
Step 2: Create login.tmpl
Login page with a form where users can enter their username and password. Similar to the registration page, the login.tmpl
HTML template displays any flash messages on the page.
<!-- views/login.tmpl -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<h2>Login</h2>
<!-- Flash messages -->
{{ with .Flash }}
{{ range . }}
<div class="flash-message">{{ . }}</div>
{{ end }}
{{ end }}
<form action="/api/login" method="POST">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required><br>
<button type="submit">Login</button>
</form>
<!-- Registration link -->
<p>Don't have an account? <a href="/register">Register here</a></p>
</body>
</html>
Step 3: Create profile.tmpl
The profile page features a form where users can update their name and email, and it will display any flash messages.
<!-- views/profile.tmpl -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Profile</title>
</head>
<body>
<h2>Profile</h2>
<!-- Flash messages -->
{{ with .Flash }}
{{ range . }}
<div class="flash-message">{{ . }}</div>
{{ end }}
{{ end }}
<form action="/web/user" method="POST" enctype="application/x-www-form-urlencoded">
<!-- Your form fields here -->
<label for="name">Name:</label>
<input type="text" id="name" name="name" value="{{.Name}}" required><br>
<label for="email">Email:</label>
<input type="email" id="email" name="email" value="{{.Email}}" required><br>
<button type="submit">Update Profile</button>
</form>
</body>
</html>
Step 4: Create AuthMiddleware.go
The AuthMiddleware.go
file is a middleware in Go Gin designed to handle session-based authentication. This middleware ensures that certain routes or actions are only accessible by authenticated users. It redirects unauthenticated users to the login page.
// middlewares/AuthMiddleware.go
package middlewares
import (
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
// AuthMiddleware is a middleware to check session for authentication
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)
// Check if the "user" key is present in the session
user := session.Get("user")
if user == nil {
// Redirect to the login page if not authenticated
c.Redirect(http.StatusSeeOther, "/login")
c.Abort() // Stop the execution of subsequent middleware and the handler
return
}
// Continue with the request if authenticated
c.Next()
}
}
Step 5: Create FlashMessageMiddleware.go
The FlashMessageMiddleware.go
file is another middleware in Go Gin, and it specifically adds flash message functionality to the Gin context. This middleware provides functions to set and retrieve flash messages, enhancing the user experience by allowing the display of success or error messages.
// middlewares/FlashMessageMiddleware.go
package middlewares
import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
// FlashMessageMiddleware adds flash message functionality to the Gin context
func FlashMessageMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Function to set flash messages
c.Set("setFlashMessage", func(message string) {
session := sessions.Default(c)
session.AddFlash(message)
session.Save()
})
// Function to get flash messages
c.Set("getFlashMessages", func() []string {
session := sessions.Default(c)
flashes := session.Flashes()
messages := make([]string, len(flashes))
for i, flash := range flashes {
messages[i] = flash.(string)
}
session.Save()
return messages
})
c.Next()
}
}
Moreover, these middleware components play a crucial role in enhancing the security and user interaction aspects of your Go Gin application. The AuthMiddleware ensures that only authenticated users can access certain routes, and FlashMessageMiddleware facilitates the display of informative messages across different requests.
Step 6: Create RenderTemplate.go
The RenderTemplate.go
file is a utility in your Go Gin application that has been updated to handle rendering templates along with flash messages. It facilitates the process of rendering HTML templates while incorporating flash messages for improved user interaction.
// utils/RenderTemplate.go
package utils
import (
"github.com/gin-gonic/gin"
"net/http"
)
// RenderTemplate is a custom function to render a template with flash messages
func RenderTemplate(c *gin.Context, templateName string, data gin.H) {
// Fetch flash messages from the template context
flashMessages := c.MustGet("getFlashMessages").(func() []string)()
// Add flash messages to the data to be passed to the template
data["Flash"] = flashMessages
// Render the template
c.HTML(http.StatusOK, templateName, data)
}
This utility becomes handy when you want to render HTM templates and include flash messages, improving the user experience with informative messages.
Building A Web Application With Go, Gin, And JWT Authentication – Part 2
An additional function ExtractToken
has been added to token.go
. This function extracts the token from the Gin context, considering both query parameters and the “Authorization” header. It’s a utility function that simplifies the process of retrieving tokens from various sources within your application.
// token.go
func ExtractToken(c *gin.Context) string {
token := c.Query("token")
if token != "" {
return token
}
bearerToken := c.Request.Header.Get("Authorization")
if len(strings.Split(bearerToken, " ")) == 2 {
return strings.Split(bearerToken, " ")[1]
}
return ""
}
The function makes it easier to obtain tokens from different locations within the HTTP request.
Step 8: Update AuthController.go
The AuthController.go
file has been extended to include registration handling. This controller manages the authentication-related functionalities in your Go Gin application.
// controllers/AuthController.go (continued)
package controllers
import (
"github.com/gin-gonic/gin"
"mywebapp/models"
"mywebapp/utils"
"net/http"
"github.com/gin-contrib/sessions"
)
// RegisterIndex displays the registration form
func RegisterIndex(c *gin.Context) {
utils.RenderTemplate(c, "register.tmpl", gin.H{
"title": "Register",
})
}
// Register handles user registration
func Register(c *gin.Context) {
var input models.RegisterInput
if err := c.ShouldBind(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Check if passwords match
if input.Password != input.ConfirmPassword {
c.JSON(http.StatusBadRequest, gin.H{"error": "Passwords do not match"})
return
}
// Create a new user
user, err := models.CreateUser(input.Username, input.Password)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Registration successful", "user": user})
}
// RegisterForm handle user registration via form
func RegisterForm(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
name := c.PostForm("name")
email := c.PostForm("email")
// Create a new user
user := models.User{
Username: username,
Password: password,
Name: name,
Email: email,
}
// Save the user to the database
_, err := user.SaveUser()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
flashMessage := c.MustGet("setFlashMessage").(func(string))
flashMessage("Register successfully")
redirectURL := "/login"
c.Redirect(http.StatusSeeOther, redirectURL)
}
// Login Index Page
func LoginIndex (c *gin.Context) {
utils.RenderTemplate(c, "login.tmpl", gin.H{
"title": "Login",
})
}
// LoginWithSession handles user login and sets a session
func LoginWithSession(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
// Authenticate user
token, err := models.LoginCheck(username, password)
if err != nil {
// Authentication failed, return an error response
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"})
return
}
// Set a session variable
session := sessions.Default(c)
session.Set("token", token)
session.Set("user", username)
session.Save()
redirectURL := "/dashboard/profile"
c.Redirect(http.StatusSeeOther, redirectURL)
}
This includes register, and login rendering function, as well as registration, login with session setting functionalities.
Step 9: Create ProfileController.go
A new controller, ProfileController.go
, has been added to manage user profiles and updates.
package controllers
import (
"net/http"
"strings"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"ginjwtauth/models"
"ginjwtauth/utils"
)
// ProfileInput represents the input for the profile update endpoint
type ProfileInput struct {
Name string `form:"name" binding:"required"`
Email string `form:"email" binding:"required"`
}
// Profile displays the user's profile
func ProfileIndex(c *gin.Context) {
session := sessions.Default(c)
username := session.Get("user").(string)
user, err := models.GetUserByUsername(username)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "User not found"})
return
}
utils.RenderTemplate(c, "profile.tmpl", gin.H{
"Name": user.Name,
"Email": user.Email,
})
}
// UpdateProfile updates the user profile based on the provided input
func UpdateProfile(c *gin.Context) {
var (
userID uint
err error
)
session := sessions.Default(c)
username := session.Get("user")
// Retrieve user
if username != nil {
user, err := models.GetUserByUsername(username.(string))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "User not found"})
return
}
userID = user.ID
} else {
userID, err = utils.ExtractTokenID(utils.ExtractToken(c))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error1": err.Error()})
return
}
}
var input ProfileInput
// Use ShouldBind with Form instead of ShouldBindQuery
if err := c.ShouldBind(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
u, err := models.GetUserByID(userID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error3": err.Error()})
return
}
u.Name = input.Name
u.Email = input.Email
_, saveErr := u.UpdateUser()
if saveErr != nil {
c.JSON(http.StatusBadRequest, gin.H{"error4": saveErr.Error()})
return
}
// Check if the request is from the web (HTML form)
if strings.Contains(c.Request.URL.Path, "/web/") {
// Redirect to a different page
flashMessage := c.MustGet("setFlashMessage").(func(string))
flashMessage("Profile updated successfully")
redirectURL := "/dashboard/profile"
c.Redirect(http.StatusSeeOther, redirectURL)
} else {
// Respond with JSON for API requests
c.JSON(http.StatusOK, gin.H{"message": "Profile updated successfully"})
}
}
This controller manages the user’s profile display and the update process based on the provided input. It includes functions for rendering the profile page and handling profile updates.
Step 10: Update main.go
Install Session Package
go get github.com/gin-contrib/session
Import Session Packages. The sessions
package provides general session support, while cookie
is a specific session store implementation using cookies.
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
Set up Session Middleware. This middleware will handle session-related tasks, such as creating and managing user sessions.
// Set up session middleware
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("mysession", store))
Use FlashMessageMiddleware. This line adds the FlashMessageMiddleware
to the Gin engine.
// Set up FlashMessageMiddleware
r.Use(middlewares.FlashMessageMiddleware())
Load HTML Templates. This line tells Gin to load HTML templates from the views directory. It’s crucial for rendering HTML pages with dynamic content.
// Load templates
r.LoadHTMLGlob("views/*")
Profile Routes with JWT Authentication Middleware. These routes are related to user profiles and are protected by JWT authentication middleware (JwtAuthMiddleware
). The middleware ensures that requests to these routes include a valid JWT token.
api.GET("/profile", middlewares.JwtAuthMiddleware(), controllers.Profile)
api.GET("/user", middlewares.JwtAuthMiddleware(), controllers.Profile)
api.POST("/user", middlewares.JwtAuthMiddleware(), controllers.UpdateProfile)
Furthermore, define web routes for the web interface. The /login
and /register
routes serve the login and registration pages, while the “/dashboard/profile” route is protected by AuthMiddleware, displaying the user profile page. Additionally, the route /web/user serves for updating user profiles.
r.GET("/login", controllers.LoginIndex)
r.GET("/register", controllers.RegisterIndex)
dashboard := r.Group("/dashboard")
dashboard.Use(middlewares.AuthMiddleware())
dashboard.GET("/profile", controllers.ProfileIndex)
web := r.Group("/web")
{
web.Use(middlewares.AuthMiddleware())
web.POST("/user", controllers.UpdateProfile)
}
New login route with session, and register route to handle form submission. Login route sets a session variable upon successful login.
// New login route that sets a session
r.POST("/api/login", controllers.LoginWithSession)
// New register route for form
r.POST("/api/register", controllers.RegisterForm)
Full Final main.go
// main.go (continued)
package main
import (
"github.com/gin-gonic/gin"
"mywebapp/controllers"
"mywebapp/middlewares"
"mywebapp/utils"
)
func main() {
// Initialize the Gin router
r := gin.Default()
// Connect to the database
models.ConnectDataBase()
// Set up FlashMessageMiddleware
r.Use(middlewares.FlashMessageMiddleware())
// Load templates
r.LoadHTMLGlob("views/*")
// Set up session middleware
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("mysession", store))
// Group Api routes
api := r.Group("/api")
{
auth := api.Group("/auth")
{
// Register and login routes
auth.POST("/register", controllers.Register)
auth.POST("/login", controllers.Login)
}
// Profile route with JWT authentication middleware
api.GET("/profile", middlewares.JwtAuthMiddleware(), controllers.Profile)
api.GET("/user", middlewares.JwtAuthMiddleware(), controllers.Profile)
api.POST("/user", middlewares.JwtAuthMiddleware(), controllers.UpdateProfile)
}
r.GET("/login", controllers.LoginIndex)
r.GET("/register", controllers.RegisterIndex)
dashboard := r.Group("/dashboard")
dashboard.Use(middlewares.AuthMiddleware())
dashboard.GET("/profile", controllers.ProfileIndex)
web := r.Group("/web")
{
web.Use(middlewares.AuthMiddleware())
web.POST("/user", controllers.UpdateProfile)
}
// New login route that sets a session
r.POST("/api/login", controllers.LoginWithSession)
// New register route for form
r.POST("/api/register", controllers.RegisterForm)
// Run the application on port 8082
r.Run(":8080")
}
This is the final version of the main.go
file, combining all the changes and additions made during the steps. It includes routes for user authentication, profile management, and web interface functionality.
This completes the second part of our tutorial. Users can now register, log in, and update their profiles with ease. Please feel free to visit my GitHub to get the full source code.
Thank you for exploring FastDT. Explore our range of services to enhance your business.
Learn more about our services.