Skip to content

Commit

Permalink
Merge pull request #267 from 99designs/authentication-docs
Browse files Browse the repository at this point in the history
Authentication docs
  • Loading branch information
vektah authored Aug 7, 2018
2 parents c57619e + 1871c4c commit 5cdbc97
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 30 deletions.
6 changes: 6 additions & 0 deletions docs/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ menu:
- name: Introduction
url: /
weight: -10
- name: Reference
identifier: reference
weight: 5
- name: Recipes
identifier: recipes
weight: 10
115 changes: 115 additions & 0 deletions docs/content/recipes/authentication.md
Original file line number Diff line number Diff line change
@@ -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
}
```
2 changes: 1 addition & 1 deletion docs/content/recipes/cors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion docs/content/reference/dataloaders.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion docs/content/reference/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/content/reference/scalars.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
33 changes: 16 additions & 17 deletions docs/layouts/_default/baseof.html
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
<!DOCTYPE html>
<html>
<head {{ with .Site.LanguageCode }}lang="{{ . }}"{{ end }}>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9"/>
{{- if .Params.description }}
<meta name="description" content="{{ .Params.description }}">
{{- else }}
<meta name="description" content="{{ .Site.Params.description }}">
{{- end }}

<title>{{ if not .IsHome }}{{ .Title }} &mdash;{{ end }} {{ .Site.Title }}</title>
<head {{ with .Site.LanguageCode }}lang="{{ . }}" {{ end }}>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" /> {{- if .Params.description }}
<meta name="description" content="{{ .Params.description }}"> {{- else }}
<meta name="description" content="{{ .Site.Params.description }}"> {{- end }}

<link href="https://fonts.googleapis.com/css?family=Roboto:400,400i|Source+Code+Pro:400,700|Work+Sans:600,700" rel="stylesheet">
<link rel="stylesheet" href="{{ .Site.BaseURL }}main.css?v=3" type="text/css"/>
<link rel="stylesheet" href="{{ .Site.BaseURL }}syntax.css?v=2" type="text/css"/>
<title>{{ if not .IsHome }}{{ .Title }} &mdash;{{ end }} {{ .Site.Title }}</title>

<link href="https://fonts.googleapis.com/css?family=Roboto:400,400i,700|Source+Code+Pro:400,700|Work+Sans:600,700" rel="stylesheet">
<link rel="stylesheet" href="{{ .Site.BaseURL }}main.css?v=3" type="text/css" />
<link rel="stylesheet" href="{{ .Site.BaseURL }}syntax.css?v=2" type="text/css" />

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-116208894-2"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());

gtag('config', 'UA-116208894-2');
Expand All @@ -29,10 +27,11 @@
</head>

<body>
{{ partial "sidebar" . }}
{{ block "main" . }}{{ end }}
{{ partial "sidebar" . }} {{ block "main" . }}{{ end }}
<footer>
&copy; {{ now.Format "2006" }} <a href="https://github.com/vektah">Adam Scarr</a>
&copy; {{ now.Format "2006" }}
<a href="https://github.com/vektah">Adam Scarr</a>
</footer>
</body>
</html>

</html>
8 changes: 4 additions & 4 deletions docs/layouts/partials/sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@
{{- range .Site.Menus.main }}
<li class="{{ if $currentPage.IsMenuCurrent "main" . }}active{{ end }}">
{{ if .URL }}
<a href="{{ .URL }}">{{ .Name }}</a>
<a class="menu-item" href="{{ .URL }}">{{ .Name }}</a>
{{ else }}
<span>{{ .Name }}</span>
<span class="menu-item submenu-heading">{{ .Name }}</span>
{{ end }}
{{- if .HasChildren }}
<ul class="submenu">
{{- range .Children }}
<li class="{{ if $currentPage.IsMenuCurrent "main" . }}active{{ end }}">
{{ if .URL }}
<a href="{{ .URL }}">{{ .Name }}</a>
<a class="menu-item" href="{{ .URL }}">{{ .Name }}</a>
{{ else }}
<span>{{ .Name }}</span>
<span class="menu-item">{{ .Name }}</span>
{{ end }}
</li>
{{- end }}
Expand Down
23 changes: 19 additions & 4 deletions docs/static/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -175,31 +175,46 @@ nav {

.menu a {
color: inherit;
display: block;
padding: 5px 10px;
}

.menu a:hover {
border-bottom: none;
}

.menu-item {
display: block;
padding: 5px 10px;
}

.submenu .menu-item {
padding: 5px 20px;
}

.submenu-heading {
margin-top: 15px;
}

ul.menu {
margin-left:0;
list-style: none;
}

ul.submenu {
margin-left: 15px;
margin-left: 0;
list-style: none;
margin-bottom: 0;
}

ul.submenu span {
padding: 5px 10px;
}

ul.menu li {
font-weight: 400;
}

ul.menu li.active,
ul.menu li:hover {
ul.menu a:hover {
background-color: var(--color-nav-active);
}

Expand Down

0 comments on commit 5cdbc97

Please sign in to comment.