github.com/bowd/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.