github.com/vektah/gqlgen@v0.7.2/docs/content/recipes/authentication.md (about) 1 --- 2 title: "Providing authentication details through context" 3 description: How to using golang context.Context to authenticate users and pass user data to resolvers. 4 linkTitle: Authentication 5 menu: { main: { parent: 'recipes' } } 6 --- 7 8 We have an app where users are authenticated using a cookie in the HTTP request, and we want to check this authentication status somewhere in our graph. Because GraphQL is transport agnostic we can't assume there will even be an HTTP request, so we need to expose these authention details to our graph using a middleware. 9 10 11 ```go 12 package auth 13 14 import ( 15 "database/sql" 16 "net/http" 17 "context" 18 ) 19 20 // A private key for context that only this package can access. This is important 21 // to prevent collisions between different context uses 22 var userCtxKey = &contextKey{"user"} 23 type contextKey struct { 24 name string 25 } 26 27 // A stand-in for our database backed user object 28 type User struct { 29 Name string 30 IsAdmin bool 31 } 32 33 // Middleware decodes the share session cookie and packs the session into context 34 func Middleware(db *sql.DB) func(http.Handler) http.Handler { 35 return func(next http.Handler) http.Handler { 36 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 37 c, err := r.Cookie("auth-cookie") 38 39 // Allow unauthenticated users in 40 if err != nil || c == nil { 41 next.ServeHTTP(w, r) 42 return 43 } 44 45 userId, err := validateAndGetUserID(c) 46 if err != nil { 47 http.Error(w, "Invalid cookie", http.StatusForbidden) 48 return 49 } 50 51 // get the user from the database 52 user := getUserByID(db, userId) 53 54 // put it in context 55 ctx := context.WithValue(r.Context(), userCtxKey, user) 56 57 // and call the next with our new context 58 r = r.WithContext(ctx) 59 next.ServeHTTP(w, r) 60 }) 61 } 62 } 63 64 // ForContext finds the user from the context. REQUIRES Middleware to have run. 65 func ForContext(ctx context.Context) *User { 66 raw, _ := ctx.Value(userCtxKey).(*User) 67 return raw 68 } 69 ``` 70 71 **Note:** `getUserByID` and `validateAndGetUserID` have been left to the user to implement. 72 73 Now when we create the server we should wrap it in our authentication middleware: 74 ```go 75 package main 76 77 import ( 78 "net/http" 79 80 "github.com/99designs/gqlgen/example/starwars" 81 "github.com/99designs/gqlgen/handler" 82 "github.com/go-chi/chi" 83 ) 84 85 func main() { 86 router := chi.NewRouter() 87 88 router.Use(auth.Middleware(db)) 89 90 router.Handle("/", handler.Playground("Starwars", "/query")) 91 router.Handle("/query", 92 handler.GraphQL(starwars.NewExecutableSchema(starwars.NewResolver())), 93 ) 94 95 err := http.ListenAndServe(":8080", router) 96 if err != nil { 97 panic(err) 98 } 99 } 100 ``` 101 102 And in our resolvers (or directives) we can call `ForContext` to retrieve the data back out: 103 ```go 104 105 func (r *queryResolver) Hero(ctx context.Context, episode Episode) (Character, error) { 106 if user := auth.ForContext(ctx) ; user == nil || !user.IsAdmin { 107 return Character{}, fmt.Errorf("Access denied") 108 } 109 110 if episode == EpisodeEmpire { 111 return r.humans["1000"], nil 112 } 113 return r.droid["2001"], nil 114 } 115 ``` 116 117 Things are different with websockets, and if you do things in the vein of the above example, you have to compute this at every call to `auth.ForContext`. 118 119 ```golang 120 // ForContext finds the user from the context. REQUIRES Middleware to have run. 121 func ForContext(ctx context.Context) *User { 122 raw, ok := ctx.Value(userCtxKey).(*User) 123 124 if !ok { 125 payload := handler.GetInitPayload(ctx) 126 if payload == nil { 127 return nil 128 } 129 130 userId, err := validateAndGetUserID(payload["token"]) 131 if err != nil { 132 return nil 133 } 134 135 return getUserByID(db, userId) 136 } 137 138 return raw 139 } 140 ``` 141 142 It's a bit inefficient if you have multiple calls to this function (e.g. on a field resolver), but what you might do to mitigate that is to have a session object set on the http request and only populate it upon the first check.