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.
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
).
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
type CookieAccess struct {
Writer http.ResponseWriter
IsLoggedIn bool
UserId uint64
}
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()
}
}
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
}