gogo-gingqlgen

gqlgen Set cookie from resolver


I'm using gin and gqlgen. I was need to set cookie from resolver but all I have in my resolver is context and inputs from graphQL. This question is already answered in github. But this one is different because I can't change ctx.Writer.Write and nothing when you try to pass in ctx.Next. because gin does not work like that.

func (r *mutationResolver) Login(ctx context.Context, email string, password string) (bool, error) {
        // You need ctx.Writer to set a cookie and can't access that from here
}

I have solved this issue and I want to answer my own question below.


Solution

  • Explanation

    How serverside cookie works:

    Server-side cookies are embedded within the response headers by the server, signaling to the browser its intent to set a cookie. This mechanism aims to enable the utilization of ctx.Writer directly within the resolver, bypassing the need for middleware intervention.

    In the middleware, you are provided with a context (ctx) that contains ctx.Request.Context(). You also have access to ctx.Writer, which is necessary for modifying headers to set cookies. To make ctx.Writer available in your resolver, you should embed it within ctx.Request.Context(). This approach is essential because only the ctx.Request is passed to your resolver, not the entire context (ctx).

    The code


    This struct will use to create in the middleware and pass a pointer of it to the resolver, in all the routes you will know if a user is authenticated or not, and also will be able to use the http.ResponseWriter

    Struct

    type CookieAccess struct {
        Writer     http.ResponseWriter
        IsLoggedIn bool
        UserId     uint64
    }
    
    

    Middleware

    func setValInCtx(ctx *gin.Context, val interface{}) {
        newCtx := context.WithValue(ctx.Request.Context(), "ctxCookieAccessKey", val)
        ctx.Request = ctx.Request.WithContext(newCtx)
    }
    
    func Middleware() gin.HandlerFunc {
        return func(ctx *gin.Context) {
            cookieA := CookieAccess{
                Writer: ctx.Writer,
                IsLoggedIn: false, // Assume not logged in by default
                UserId: 0,
            }
    
            setValInCtx(ctx, &cookieA)
    
            c, err := ctx.Request.Cookie("tokenKey")
            if err != nil {
                // If there's an error fetching the cookie, log it and proceed
                log.Printf("Error fetching 'tokenKey' cookie: %v", err)
                ctx.Next()
                return
            }
    
            // Proceed with token parsing only if the cookie is successfully fetched
            rawToken := c.Value
            userId, err := ParseToken(rawToken)
            if err != nil {
                // If there's an error parsing the token, log it and ensure user is not logged in
                log.Printf("Error parsing token: %v", err)
            } else {
                // If token is successfully parsed, mark user as logged in and set UserId
                cookieA.IsLoggedIn = true
                cookieA.UserId = userId
            }
    
            ctx.Next()
        }
    }
    
    

    Resolvers

    
    func (r *MutationResolver) logginOrConsumerResolver(ctx context.Context, input Credentials) (*Response, error) {
        // ctx.Request.Context() from middleware is here
        ca := ctx.Value("ctxCookieAccessKey").(*CookieAccess)
        if ca.IsLoggedIn {
            // this is the way to get token proceed result in other resolvers
            return nil, fmt.Errorf("you are already have a valid token, userId: %v", ca.userId)
        }
    
        http.SetCookie(ca.Writer, &http.Cookie{
            Name:     "tokenKey",
            Value:    token, // make your token
            HttpOnly: true,
            Path:     "/",
            Expires:  time.Now().Add(24 * time.Hour),
        })
    
        return nil, nil
    }