github.com/99designs/gqlgen@v0.17.45/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 authentication 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/_examples/starwars" 81 "github.com/99designs/gqlgen/graphql/handler" 82 "github.com/99designs/gqlgen/graphql/playground" 83 "github.com/go-chi/chi" 84 ) 85 86 func main() { 87 router := chi.NewRouter() 88 89 router.Use(auth.Middleware(db)) 90 91 srv := handler.NewDefaultServer(starwars.NewExecutableSchema(starwars.NewResolver())) 92 router.Handle("/", playground.Handler("Starwars", "/query")) 93 router.Handle("/query", srv) 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 ### Websockets 118 119 If you need access to the websocket init payload you can add your `InitFunc` in `AddTransport`. 120 Your InitFunc implementation could then attempt to extract items from the payload: 121 122 ```go 123 package main 124 125 import ( 126 "context" 127 "errors" 128 "log" 129 "net/http" 130 "os" 131 "time" 132 133 "github.com/99designs/gqlgen/graphql/handler" 134 "github.com/99designs/gqlgen/graphql/handler/extension" 135 "github.com/99designs/gqlgen/graphql/handler/transport" 136 "github.com/99designs/gqlgen/graphql/playground" 137 "github.com/go-chi/chi" 138 "github.com/gorilla/websocket" 139 "github.com/gqlgen/_examples/websocket-initfunc/server/graph" 140 "github.com/gqlgen/_examples/websocket-initfunc/server/graph/generated" 141 "github.com/rs/cors" 142 ) 143 144 func webSocketInit(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) { 145 // Get the token from payload 146 any := initPayload["authToken"] 147 token, ok := any.(string) 148 if !ok || token == "" { 149 return nil, errors.New("authToken not found in transport payload") 150 } 151 152 // Perform token verification and authentication... 153 userId := "john.doe" // e.g. userId, err := GetUserFromAuthentication(token) 154 155 // put it in context 156 ctxNew := context.WithValue(ctx, "username", userId) 157 158 return ctxNew, nil 159 } 160 161 const defaultPort = "8080" 162 163 func main() { 164 port := os.Getenv("PORT") 165 if port == "" { 166 port = defaultPort 167 } 168 169 router := chi.NewRouter() 170 171 // CORS setup, allow any for now 172 // https://gqlgen.com/recipes/cors/ 173 c := cors.New(cors.Options{ 174 AllowedOrigins: []string{"*"}, 175 AllowCredentials: true, 176 Debug: false, 177 }) 178 179 srv := handler.New(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}})) 180 srv.AddTransport(transport.POST{}) 181 srv.AddTransport(transport.Websocket{ 182 KeepAlivePingInterval: 10 * time.Second, 183 Upgrader: websocket.Upgrader{ 184 CheckOrigin: func(r *http.Request) bool { 185 return true 186 }, 187 }, 188 InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) { 189 return webSocketInit(ctx, initPayload) 190 }, 191 }) 192 srv.Use(extension.Introspection{}) 193 194 router.Handle("/", playground.Handler("My GraphQL App", "/app")) 195 router.Handle("/app", c.Handler(srv)) 196 197 log.Printf("connect to http://localhost:%s/ for GraphQL playground", port) 198 log.Fatal(http.ListenAndServe(":"+port, router)) 199 } 200 ``` 201 202 > Note 203 > 204 > Subscriptions are long lived, if your tokens can timeout or need to be refreshed you should keep the token in 205 context too and verify it is still valid in `auth.ForContext`.