Background
Gin
Gin is a web framework for Go that is known for its simplicity and speed. Gin is built on top of the net/http package, uses HTTP handlers to route and handle requests, and supports middleware and utility functions for request validation and error handling.
Authentication with Gin
Authentication with JWT, Auth0, and cookies can be used to verify the identity of a user and grant access to protected resources.
- JWT (JSON Web Token) is a compact and self-contained way to transmit information between parties, typically used to authenticate a user by storing a JSON object with claims in the token.
- Auth0 is a cloud-based authentication and authorization platform that provides various authentication methods, including social logins, single sign-on, and multi-factor authentication.
- Cookies are small pieces of data that are stored on a user’s device and sent with every request to a website. They can be used to store information, such as a user’s session ID, and can be used to authenticate a user by checking for the presence of a valid cookie on subsequent requests.
All of these methods can be used together or separately to authenticate users and secure access to resources. In this article, I’ll demonstrate how to implement each of these methods and authenticate using Gin middleware.
Implementation
JWT
JWT implementation. User details (username and password) are stored in MongoDB.
We’ll start with creating a private key and a public key. This needs to be done only once so it can be part of the package init, and the key will be defined as a global variable
var key *ecdsa.PrivateKey
func init() {
var err error
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
}
Here is the full program for JWT
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
"go.mongodb.org/mongo-driver/bson"
)
type Claims struct {
Username string `json:"username"`
jwt.RegisteredClaims
}
type JWTOutput struct {
Token string `json:"token"`
Expires time.Time `json:"expires"`
}
// use for returning error message
type Message struct {
Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
}
type User struct {
Username string `json:"username"`
Password string `json:"password"`
}
var key *ecdsa.PrivateKey
func init() {
// we want to create the key only once for all users
var err error
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
}
func AuthMiddlewareJWT() gin.HandlerFunc {
// JWT ssession
return func(c *gin.Context) {
tokenValue := c.GetHeader("Authorization")
userClaims := &UserClaims{}
token, err := jwt.ParseWithClaims(tokenValue, userClaims, func(token *jwt.Token) (interface{}, error) {
return key.Public(), nil
})
if err != nil || token == nil || !token.Valid {
c.JSON(http.StatusUnauthorized, models.Message{Error: "Invalid Token"})
c.Abort()
return
}
c.Next()
}
}
func SignInHandlerJWT(c *gin.Context) {
// JWT session - create new session
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, Message{Error: err.Error()})
return
}
sha := sha256.New()
sha.Write([]byte(user.Password))
// check if user and password existing in DB
filter := bson.M{"username": user.Username, "password": sha.Sum(nil)}
cur := collection.FindOne(h.ctx, filter)
if cur.Err() != nil {
c.JSON(http.StatusUnauthorized, Message{Error: "Incorrect user or password"})
return
}
// JWT token
expirationTime := time.Now().Add(10 * time.Minute)
claims := &Claims{
Username: user.Username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
// get the string token so can return it in body
tokenString, err := token.SignedString(key)
if err != nil {
c.JSON(http.StatusInternalServerError, Message{Error: err.Error()})
return
}
jwtOutput := JWTOutput{
Token: tokenString,
Expires: expirationTime,
}
c.JSON(http.StatusOK, jwtOutput)
}
func main() {
router := gin.Default()
router.GET("/login", SignInHandlerJWT) // JWT
router.Use(AuthMiddlewareJWT()) // JWT
{
router.POST("/update", NewUpdateHandler)
router.GET("/list", GetListHandler)
}
router.Run()
}
Cookie session
Cookie session implementation. We use Redis to store the cookie sessions and MongoDB to store usernames and passwords.
import (
"context"
"os"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-contrib/sessions"
redisStore "github.com/gin-contrib/sessions/redis"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
// use for returning error message
type Message struct {
Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
}
type User struct {
Username string `json:"username"`
Password string `json:"password"`
}
func SignInHandlerCookie(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, Message{Message: err.Error()})
return
}
// encrypt password
sha := sha256.New()
sha.Write([]byte(user.Password))
// fetch from mongo the user and password
filter := bson.M{"username": user.Username, "password": sha.Sum(nil)}
cur := collection.FindOne(ctx, filter)
if cur.Err() != nil {
c.JSON(http.StatusUnauthorized, Message{Error: "Incorrect username or password"})
return
}
// session cookie
sessionToken := xid.New().String()
session := sessions.Default(c)
session.Set("username", user.Username)
session.Set("token", sessionToken)
session.Options(sessions.Options{MaxAge: 10 * 60}) // 10 minutes age
if err := session.Save(); err != nil {
c.JSON(http.StatusInternalServerError, Message{Error: err.Error()})
return
}
c.JSON(http.StatusOK, User{Username: user.Username})
}
func AuthMiddlewareCookie() gin.HandlerFunc {
// Cookie session
return func(c *gin.Context) {
session := sessions.Default(c)
sessionToken := session.Get("token")
if sessionToken == nil {
c.JSON(http.StatusForbidden, Message{Error: "User not logged in"})
c.Abort()
}
c.Next()
}
}
func SignOutHandlerCookie(c *gin.Context) {
session := sessions.Default(c)
session.Clear()
if err := session.Save(); err != nil {
c.JSON(http.StatusInternalServerError, Message{Error: err.Error()})
return
}
c.JSON(http.StatusOK, models.Message{Message: "signed out"})
}
func main() {
router := gin.Default()
// cookies
store, _ := redisStore.NewStore(10, "tcp", os.Getenv("REDIS_ADDR"), "", []byte("secret"))
router.Use(sessions.Sessions("cookie_api", store))
router.POST("/login", authHandler.SignInHandlerCookie) // Cookie
router.Use(AuthMiddlewareCookie()) // Cookie
{
router.POST("/signout", SignOutHandlerCookie) // Cookie
router.POST("/update", NewUpdateHandler)
router.GET("/list", GetListHandler)
}
router.Run()
}
Auth0
import (
"github.com/gin-gonic/gin"
"github.com/auth0-community/go-auth0"
"gopkg.in/square/go-jose.v2"
)
// use for returning error message
type Message struct {
Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
}
type User struct {
Username string `json:"username"`
Password string `json:"password"`
}
func AuthMiddlewareAuth0() gin.HandlerFunc {
return func(c *gin.Context) {
var auth0Domain = "https://" + os.Getenv("AUTH0_DOMAIN") + "/"
client := auth0.NewJWKClient(auth0.JWKClientOptions{URI: auth0Domain + ".well-known/jwks.json"}, nil)
configuration := auth0.NewConfiguration(client, []string{os.Getenv("AUTH0_API_IDENTIFIER")}, auth0Domain, jose.RS256)
validator := auth0.NewValidator(configuration, nil)
_, err := validator.ValidateRequest(c.Request)
if err != nil {
c.JSON(http.StatusUnauthorized, Message{Error: "Invalid Token"})
c.Abort()
return
}
c.Next()
}
}
func main() {
router := gin.Default()
authorized := router.Group("/v1")
authorized.Use(AuthMiddlewareAuth0()) // Auth0
{
authorized.POST("/recipes", NewRecipeHandler)
authorized.GET("/recipes/:id", GetRecipeHandler)
}
router.Run()
}
Note: import section includes relevant packages only. These examples do not include packages for Mongo, Redis, and standard packages.
Code Examples
For full code examples, please check out this GitHub repo — https://github.com/ronenniv/Go-Gin-Auth