Home  >  Article  >  Backend Development  >  Golang Websocket (Gorilla) with cookie authentication

Golang Websocket (Gorilla) with cookie authentication

WBOY
WBOYforward
2024-02-11 18:03:081259browse

带 cookie 身份验证的 Golang Websocket (Gorilla)

In web development, authentication is an essential feature, and cookie-based authentication is a common way. As an efficient and concise programming language, Golang has powerful web development capabilities. This article will introduce how to use the Gorilla toolkit to implement the Websocket function with cookie authentication in Golang, making your application more secure and reliable. Whether you are a Golang beginner or an experienced developer, this article can help you get started quickly. Let’s take a look!

Question content

I'm trying to use gorilla websocket to launch a chart. Authentication middleware works through cookies and jwt tokens. All my endpoints over http work, but websocket does not. After reading a lot of topics like gorilla websocket with cookie authentication, I found that my cookies were empty and the context in the websocket connection was also empty. I do not understand why? Can anyone explain why? p.s.: I tried removing the upgrade from that handler and the cookie and context came through successfully, but after upgrading the connection to the websocket protocol it failed. This is my file: Endpoint:

func (r *router) routes(engine *gin.engine) {
    engine.use(r.handler.verifyuser())

    engine.post("/signup", r.handler.createuser)
    engine.post("/signin", r.handler.loginuser)
    engine.get("/welcome", r.handler.welcome)
    engine.get("/logout", r.handler.logout)

    engine.post("/ws/createroom", r.wshandler.createroom)
    engine.get("/ws/joinroom/:roomid", r.wshandler.joinroom)
}

ws_handler

func (h *handler) joinroom(c *gin.context) {
    claims := c.request.context().value("jwt").(models.claims) //couldn't find value with "jwt" key
    fmt.println(claims.id, claims.name)
    cookie, err := c.cookie("chartjwt") // allways err no cookie
    if err != nil {
        fmt.printf("no cookie, error:%v\n", err)
    }
    fmt.printf("cookie: %+v\n", cookie)
    conn, err := upgrader.upgrade(c.writer, c.request, nil)
    if err != nil {
        c.json(http.statusbadrequest, gin.h{"error": err.error()})
        return
    }

middleware:

func (h *handler) verifyuser() gin.handlerfunc {
    return func(c *gin.context) {
        notauth := []string{"/signup", "/signin"}
        requestpath := c.request.url.path

        for _, val := range notauth {
            if val == requestpath {
                c.next()
                return
            }
        }

        token, err := c.cookie("chartjwt")
        if err != nil {
            c.redirect(http.statuspermanentredirect, signinpage)
        }

        claims, ok := validatetoken(token)
        if !ok {
            c.json(http.statusbadrequest, gin.h{"error": errors.new("invalid token")})
            return
        }

        c.request = c.request.withcontext(context.withvalue(c.request.context(), "jwt", *claims))

        c.next()
    }
}

All other endpoints work, if you need any other code please let me know. I don't want to make my problem more complicated because I thought it was simple but I misunderstood something ( Thanks for your help and advice.

p.s.: If I turn off the middleware, everything works as expected.

renew: Added validation and generation functions

func validatetoken(jwttoken string) (*models.claims, bool) {
    claims := &models.claims{}

    token, err := jwt.parsewithclaims(jwttoken, claims, func(token *jwt.token) (interface{}, error) {
        return []byte(config.secretkey), nil
    })
    if err != nil {
        return claims, false
    }

    if !token.valid {
        return claims, false
    }

    return claims, true
}

func (h *handler) generatetokenstringforuser(id, name string) (string, error) {
    // create the jwt claims, which includes the username and expiry time
    claims := models.claims{
        id:   id,
        name: name,
        registeredclaims: jwt.registeredclaims{
            issuer:    id,
            expiresat: jwt.newnumericdate(time.now().add(24 * time.hour)),
        },
    }

    token := jwt.newwithclaims(jwt.signingmethodhs256, claims)
    tokenstring, err := token.signedstring([]byte(config.secretkey))
    return tokenstring, err
}

Added login functionality where I added a cookie with jwt string

func (h *handler) LoginUser(c *gin.Context) {
    var input models.LoginUserReq

    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    res, err := h.Service.LoginUser(context.Background(), &input)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    token, err := h.generateTokenStringForUser(res.ID, res.Name)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.SetCookie("chartJWT", token, 60*60*24, "/", "localhost", false, true)
    c.JSON(http.StatusOK, gin.H{"user": res})
}

Update after help: The problem was that in my postman setup request, I didn't specify the cookie correctly.

Solution

Let me try to help figure out the problem. First, I've simplified your example a bit to focus only on the relevant parts. If you need to omit something, please let me know and I will update the answer. First, let me start with the jwt token generation/verification in the local auth package.

auth/auth.go File

package auth

import (
    "fmt"
    "strings"
    "time"

    "github.com/golang-jwt/jwt"
)

func validatetoken(jwttoken string) (*jwt.mapclaims, error) {
    // parse the token
    token, err := jwt.parse(strings.replace(jwttoken, "bearer ", "", 1), func(token *jwt.token) (interface{}, error) {
        _, ok := token.method.(*jwt.signingmethodhmac)
        if !ok {
            return nil, fmt.errorf("unexpected signing method: %v", token.header["alg"])
        }
        return []byte("abcd1234!!"), nil
    })
    // err while parsing the token
    if err != nil {
        return nil, err
    }
    // token valid
    var claims jwt.mapclaims
    var ok bool
    if claims, ok = token.claims.(jwt.mapclaims); ok && token.valid {
        return &claims, nil
    }
    return nil, fmt.errorf("token not valid")
}

func generatetoken(username, password string) (string, error) {
    // todo: here you can add logic to check against a db
    //...

    // create a new token by providing the cryptographic algorithm
    token := jwt.new(jwt.signingmethodhs256)

    // set default/custom claims
    claims := token.claims.(jwt.mapclaims)
    claims["exp"] = time.now().add(24 * time.hour * 3).unix()
    claims["username"] = username
    claims["password"] = password

    tokenstring, err := token.signedstring([]byte("abcd1234!!"))
    if err != nil {
        return "", err
    }
    return tokenstring, nil
}

Now, let’s move to the middleware part.

middlewares/middlewares.go File

package middlewares

import (
    "net/http"

    "websocketauth/auth"

    "github.com/gin-gonic/gin"
)

func verifyuser() gin.handlerfunc {
    return func(c *gin.context) {
        notauth := []string{"/signin"}
        requestpath := c.request.url.path
        for _, val := range notauth {
            if val == requestpath {
                c.next()
                return
            }
        }

        token, err := c.cookie("chartjwt")
        if err != nil {
            c.redirect(http.statuspermanentredirect, "/signin")
        }

        claims, err := auth.validatetoken(token)
        if err != nil {
            c.json(http.statusbadrequest, gin.h{"error": err.error()})
            return
        }

        c.set("jwt", *claims)
        c.next()
    }
}

To be able to upload something in context, you should use the c.set(key, value) method. Now, let's turn to handlers.

handlers/handlers.go File

package handlers

import (
    "fmt"
    "net/http"

    "websocketauth/auth"

    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt"
    "github.com/gorilla/websocket"
)

var upgrader websocket.upgrader

type loginuserreq struct {
    username string `json:"username" binding:"required"`
    password string `json:"password" binding:"required"`
}

func loginuser(c *gin.context) {
    var input loginuserreq
    if err := c.shouldbind(&input); err != nil {
        c.json(http.statusbadrequest, gin.h{"error": err.error()})
        return
    }

    // i don't know what you do within the handler.service.loginuser() method

    token, err := auth.generatetoken(input.username, input.password)
    if err != nil {
        c.json(http.statusbadrequest, gin.h{"error": err.error()})
        return
    }

    c.setcookie("chartjwt", token, 60*60*24, "/", "localhost", false, true)
    c.json(http.statusok, gin.h{"user": token})
}

func joinroom(c *gin.context) {
    claims := c.mustget("jwt").(jwt.mapclaims)
    fmt.println("username", claims["username"])
    fmt.println("password", claims["password"])
    ws, err := upgrader.upgrade(c.writer, c.request, nil)
    if err != nil {
        panic(err)
    }
    charttoken, err := c.cookie("chartjwt")
    if err != nil {
        panic(err)
    }
    fmt.println("charttoken", charttoken)
    _ = ws
}

The missing parts are skipped since I don't know what they do, such as the handler.service.loginuser() method. To correctly read content from the context, you must use the c.mustget(key) method.

main.go File

package main

import (
    "websocketauth/handlers"
    "websocketauth/middlewares"

    "github.com/gin-gonic/gin"
    "github.com/gorilla/websocket"
)

func main() {
    handlers.Upgrader = websocket.Upgrader{
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
    }
    gin.SetMode(gin.DebugMode)
    r := gin.Default()

    r.Use(middlewares.VerifyUser())
    r.GET("/join-room", handlers.JoinRoom)
    r.POST("/signin", handlers.LoginUser)

    r.Run(":8000")
}

This is the setting logic. Nothing worth mentioning here.
If you need any other help, please let me know, thank you!

The above is the detailed content of Golang Websocket (Gorilla) with cookie authentication. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete