gqlgen Set cookie from resolver

Issue

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

Short answer

Explanation about server side cookie: cookies are set via headers, that’s how the browser understands that you want to set a cookie, and you need ctx.Writter to write headers. at the end, "you want to access ctx.Writter in your resolver".

You have a ctx in your middleware(In my case it’s gin and it’s ctx.Request.Context()), And you have a ctx.Writer, which is what you need in order to write to your headers and set the cookie.

You should put your ctx.Writer into your ctx.Request.Context(), because your ctx.Request is going forward to your resolver, not the hole ctx!

And then you can access your Writer.

Full answer

In middleware you must build a struct object, pass ctx.Writer into it and set a pointer to ctx.Request.Context and set a method to set cookie for you.

type CookieAccess struct {
    Writer     http.ResponseWriter
    UserId     uint64
    IsLoggedIn bool
}
// method to write cookie
func (this *CookieAccess) SetToken(token string) {
    http.SetCookie(this.Writer, &http.Cookie{
        Name:     cookieName,
        Value:    token,
        HttpOnly: true,
        Path:     "/",
        Expires:  time.Now().Add(token_expire),
    })
}

And in your middleware:

func extractUserId(ctx *gin.Context) (uint64, error) {
    c, err := ctx.Request.Cookie(cookieName)
    if err != nil {
        return 0, errors.New("There is no token in cookies")
    }

    userId, err := ParseToken(c.Value)
    if err != nil {
        return 0, err
    }
    return userId, nil
}

func setValInCtx(ctx *gin.Context, val interface{}) {
    newCtx := context.WithValue(ctx.Request.Context(), cookieAccessKeyCtx, val)
    ctx.Request = ctx.Request.WithContext(newCtx)
}

func Middleware() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        cookieA := CookieAccess{
            Writer: ctx.Writer,
        }

        // &cookieA is a pointer so any changes in future is changing cookieA is context
        setValInCtx(ctx, &cookieA)

        userId, err := extractUserId(ctx)
        if err != nil {
            cookieA.IsLoggedIn = false
            ctx.Next()
            return
        }

        cookieA.UserId = userId
        cookieA.IsLoggedIn = true

       // calling the actual resolver
        ctx.Next()
       // here will execute after resolver and all other middlewares was called
       // so &cookieA is safe from garbage collector
    }
}

You must call this func in your resolver.
It’s getting ctx and returning &cookieA

func GetCookieAccess(ctx context.Context) *CookieAccess {
    return ctx.Value(cookieAccessKeyCtx).(*CookieAccess)
}

And finally in your Login resolver:

CA := security.GetCookieAccess(ctx)
CA.SetToken(token)
CA.UserId = userId

I hope this will help someone :)))

Answered By – MrezaKV

Answer Checked By – Mary Flores (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.