diff --git a/docs/config.yml b/docs/config.yml index 541133103e4..57d47d5a035 100644 --- a/docs/config.yml +++ b/docs/config.yml @@ -15,3 +15,9 @@ menu: - name: Introduction url: / weight: -10 + - name: Reference + identifier: reference + weight: 5 + - name: Recipes + identifier: recipes + weight: 10 diff --git a/docs/content/recipes/authentication.md b/docs/content/recipes/authentication.md new file mode 100644 index 00000000000..7dca3dbf14f --- /dev/null +++ b/docs/content/recipes/authentication.md @@ -0,0 +1,115 @@ +--- +title: "Providing authentication details through context" +description: How to using golang context.Context to authenticate users and pass user data to resolvers. +linkTitle: Authentication +menu: { main: { parent: 'recipes' } } +--- + +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. + + +```go +package auth + +import ( + "database/sql" + "net/http" + "context" +) + +// A private key for context that only this package can access. This is important +// to prevent collisions between different context uses +var userCtxKey = &contextKey{"user"} +type contextKey struct { + name string +} + +// A stand-in for our database backed user object +type User struct { + Name string + IsAdmin bool +} + +// Middleware decodes the share session cookie and packs the session into context +func Middleware(db *sql.DB) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c, err := r.Cookie("auth-cookie") + + // Allow unauthenticated users in + if err != nil || c == nil { + next.ServeHTTP(w, r) + return + } + + userId, err := validateAndGetUserID(c) + if err != nil { + http.Error(w, "Invalid cookie", http.StatusForbidden) + return + } + + // get the user from the database + user := getUserByID(db, userId) + + // put it in context + ctx := context.WithValue(r.Context(), userCtxKey, user) + + // and call the next with our new context + r = r.WithContext(ctx) + next.ServeHTTP(w, r) + }) + } +} + +// ForContext finds the user from the context. REQUIRES Middleware to have run. +func ForContext(ctx context.Context) *User { + raw, _ := ctx.Value(userCtxKey).(*User) + return raw +} +``` + +**Note:** `getUserByID` and `validateAndGetUserID` have been left to the user to implement. + +Now when we create the server we should wrap it in our authentication middleware: +```go +package main + +import ( + "net/http" + + "github.com/99designs/gqlgen/example/starwars" + "github.com/99designs/gqlgen/handler" + "github.com/go-chi/chi" +) + +func main() { + router := chi.NewRouter() + + router.Use(auth.Middleware(db)) + + router.Handle("/", handler.Playground("Starwars", "/query")) + router.Handle("/query", + handler.GraphQL(starwars.NewExecutableSchema(starwars.NewResolver())), + ) + + err := http.ListenAndServe(":8080", router) + if err != nil { + panic(err) + } +} +``` + +And in our resolvers (or directives) we can call `ForContext` to retrieve the data back out: +```go + +func (r *queryResolver) Hero(ctx context.Context, episode Episode) (Character, error) { + if user := auth.ForContext(ctx) ; user == nil || !user.IsAdmin { + return Character{}, fmt.Errorf("Access denied") + } + + if episode == EpisodeEmpire { + return r.humans["1000"], nil + } + return r.droid["2001"], nil +} +``` diff --git a/docs/content/recipes/cors.md b/docs/content/recipes/cors.md index 1d64b13a667..e0cbd98b65c 100644 --- a/docs/content/recipes/cors.md +++ b/docs/content/recipes/cors.md @@ -2,7 +2,7 @@ title: "Setting CORS headers using rs/cors for gqlgen" description: Use the best of breed rs/cors library to set CORS headers when working with gqlgen linkTitle: CORS -menu: main +menu: { main: { parent: 'recipes' } } --- Cross-Origin Resource Sharing (CORS) headers are required when your graphql server lives on a different domain to the one your client code is served. You can read more about CORS in the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). diff --git a/docs/content/reference/dataloaders.md b/docs/content/reference/dataloaders.md index 862d1f7166e..c03c95da751 100644 --- a/docs/content/reference/dataloaders.md +++ b/docs/content/reference/dataloaders.md @@ -2,7 +2,7 @@ title: "Optimizing N+1 database queries using Dataloaders" description: Speeding up your GraphQL requests by reducing the number of round trips to the database. linkTitle: Dataloaders -menu: main +menu: { main: { parent: 'reference' } } --- Have you noticed some GraphQL queries end can make hundreds of database diff --git a/docs/content/directives.md b/docs/content/reference/directives.md similarity index 97% rename from docs/content/directives.md rename to docs/content/reference/directives.md index 9db95711ac7..fe73a70673b 100644 --- a/docs/content/directives.md +++ b/docs/content/reference/directives.md @@ -2,7 +2,7 @@ title: Using schema directives to implement permission checks description: Implementing graphql schema directives in golang for permission checks. linkTitle: Schema Directives -menu: main +menu: { main: { parent: 'reference' } } --- Directives are a bit like annotations in any other language. They give you a way to specify some behaviour without directly binding to the implementation. This can be really useful for cross cutting concerns like permission checks. diff --git a/docs/content/reference/errors.md b/docs/content/reference/errors.md index 755f4379ec0..00169834e79 100644 --- a/docs/content/reference/errors.md +++ b/docs/content/reference/errors.md @@ -2,7 +2,7 @@ linkTitle: Handling Errors title: Sending custom error data in the graphql response description: Customising graphql error types to send custom error data back to the client using gqlgen. -menu: main +menu: { main: { parent: 'reference' } } --- ## Returning errors diff --git a/docs/content/reference/scalars.md b/docs/content/reference/scalars.md index 5a126daae00..961cd748c07 100644 --- a/docs/content/reference/scalars.md +++ b/docs/content/reference/scalars.md @@ -2,7 +2,7 @@ linkTitle: Custom Scalars title: Using custom graphql types in golang description: Defining custom GraphQL scalar types using gqlgen -menu: main +menu: { main: { parent: 'reference' } } --- There are two different ways to implement scalars in gqlgen, depending on your need. diff --git a/docs/layouts/_default/baseof.html b/docs/layouts/_default/baseof.html index 4031b0fd9aa..4fb29e68095 100644 --- a/docs/layouts/_default/baseof.html +++ b/docs/layouts/_default/baseof.html @@ -1,25 +1,23 @@ - - - - {{- if .Params.description }} - - {{- else }} - - {{- end }} - {{ if not .IsHome }}{{ .Title }} —{{ end }} {{ .Site.Title }} + + + {{- if .Params.description }} + {{- else }} + {{- end }} - - - + {{ if not .IsHome }}{{ .Title }} —{{ end }} {{ .Site.Title }} + + + +