├── .gitignore ├── README.md ├── config.json.example ├── handlers.go ├── main.go ├── static └── css │ └── style.css └── templates ├── home.html └── layout.html /.gitignore: -------------------------------------------------------------------------------- 1 | config.json 2 | tmp/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang GitHub OAuth example 2 | 3 | Example code for GitHub OAuth applications. 4 | 5 | ## Instructions 6 | - `config.json.example` -> `config.json` 7 | - ensure your callback URL is sent to `/auth-callback` -------------------------------------------------------------------------------- /config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "clientID": "asdf9823u9f8u23", 3 | "clientSecret": "asdkjfaskdjflaksj", 4 | "serverSecret": "asdifj9182u98u98efua" 5 | } -------------------------------------------------------------------------------- /handlers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "encoding/gob" 8 | "fmt" 9 | "net/http" 10 | 11 | "github.com/google/go-github/github" 12 | "github.com/mitchellh/mapstructure" 13 | "golang.org/x/oauth2" 14 | ) 15 | 16 | const sessionStoreKey = "sess" 17 | 18 | func init() { 19 | gob.Register(&oauth2.Token{}) 20 | } 21 | 22 | func HomeHandler(w http.ResponseWriter, r *http.Request) { 23 | session, err := store.Get(r, sessionStoreKey) 24 | if err != nil { 25 | fmt.Fprintln(w, err) 26 | return 27 | } 28 | 29 | renderData := map[string]interface{}{} 30 | if accessToken, ok := session.Values["githubAccessToken"].(*oauth2.Token); ok { 31 | client := github.NewClient(oauthCfg.Client(oauth2.NoContext, accessToken)) 32 | 33 | user, _, err := client.Users.Get(context.Background(), "") 34 | if err != nil { 35 | fmt.Fprintln(w, err) 36 | return 37 | } 38 | 39 | renderData["github_user"] = user 40 | 41 | var userMap map[string]interface{} 42 | mapstructure.Decode(user, &userMap) 43 | renderData["github_user_map"] = userMap 44 | } 45 | 46 | tmpls["home.html"].ExecuteTemplate(w, "base", renderData) 47 | } 48 | 49 | func StartHandler(w http.ResponseWriter, r *http.Request) { 50 | b := make([]byte, 16) 51 | rand.Read(b) 52 | 53 | state := base64.URLEncoding.EncodeToString(b) 54 | 55 | session, _ := store.Get(r, sessionStoreKey) 56 | session.Values["state"] = state 57 | session.Save(r, w) 58 | 59 | url := oauthCfg.AuthCodeURL(state) 60 | http.Redirect(w, r, url, 302) 61 | } 62 | 63 | func AuthCallbackHandler(w http.ResponseWriter, r *http.Request) { 64 | session, err := store.Get(r, sessionStoreKey) 65 | if err != nil { 66 | fmt.Fprintln(w, "aborted") 67 | return 68 | } 69 | 70 | if r.URL.Query().Get("state") != session.Values["state"] { 71 | fmt.Fprintln(w, "no state match; possible csrf OR cookies not enabled") 72 | return 73 | } 74 | 75 | token, err := oauthCfg.Exchange(oauth2.NoContext, r.URL.Query().Get("code")) 76 | if err != nil { 77 | fmt.Fprintln(w, "there was an issue getting your token") 78 | return 79 | } 80 | 81 | if !token.Valid() { 82 | fmt.Fprintln(w, "retreived invalid token") 83 | return 84 | } 85 | 86 | client := github.NewClient(oauthCfg.Client(oauth2.NoContext, token)) 87 | 88 | user, _, err := client.Users.Get(context.Background(), "") 89 | if err != nil { 90 | fmt.Println(w, "error getting name") 91 | return 92 | } 93 | 94 | session.Values["githubUserName"] = user.Name 95 | session.Values["githubAccessToken"] = token 96 | session.Save(r, w) 97 | 98 | http.Redirect(w, r, "/", 302) 99 | } 100 | 101 | // http://www.gorillatoolkit.org/pkg/sessions#CookieStore.MaxAge 102 | func SessionDestroyHandler(w http.ResponseWriter, r *http.Request) { 103 | session, err := store.Get(r, sessionStoreKey) 104 | if err != nil { 105 | fmt.Fprintln(w, "aborted") 106 | return 107 | } 108 | 109 | session.Options.MaxAge = -1 110 | 111 | session.Save(r, w) 112 | http.Redirect(w, r, "/", 302) 113 | 114 | } 115 | 116 | // func sessionGithubUser(r *http.Request) error { 117 | // session, err := store.Get(r, "sess") 118 | // if err != nil { 119 | // return err 120 | // } 121 | 122 | // accessToken, ok := session.Values["githubAccessToken"].(*oauth2.Token); ok { 123 | // client := github.NewClient(oauthCfg.Client(oauth2.NoContext, accessToken)) 124 | 125 | // } 126 | 127 | // return nil 128 | // } 129 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "html/template" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | "github.com/gorilla/mux" 12 | "github.com/gorilla/sessions" 13 | "golang.org/x/oauth2" 14 | ) 15 | 16 | const ( 17 | defaultLayout = "templates/layout.html" 18 | templateDir = "templates/" 19 | 20 | defaultConfigFile = "config.json" 21 | 22 | githubAuthorizeUrl = "https://github.com/login/oauth/authorize" 23 | githubTokenUrl = "https://github.com/login/oauth/access_token" 24 | redirectUrl = "" 25 | ) 26 | 27 | type Config struct { 28 | ClientSecret string `json:"clientSecret"` 29 | ClientID string `json:"clientID"` 30 | 31 | ServerSecret string `json:"serverSecret"` 32 | } 33 | 34 | var ( 35 | cfg *Config 36 | oauthCfg *oauth2.Config 37 | store *sessions.CookieStore 38 | 39 | // scopes 40 | scopes = []string{"repo"} 41 | 42 | tmpls = map[string]*template.Template{} 43 | ) 44 | 45 | func loadConfig(file string) (*Config, error) { 46 | var config Config 47 | 48 | b, err := ioutil.ReadFile(file) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | if err = json.Unmarshal(b, &config); err != nil { 54 | return nil, err 55 | } 56 | 57 | return &config, nil 58 | } 59 | 60 | func main() { 61 | tmpls["home.html"] = template.Must(template.ParseFiles(templateDir+"home.html", defaultLayout)) 62 | 63 | var err error 64 | cfg, err = loadConfig(defaultConfigFile) 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | 69 | store = sessions.NewCookieStore([]byte(cfg.ServerSecret)) 70 | 71 | oauthCfg = &oauth2.Config{ 72 | ClientID: cfg.ClientID, 73 | ClientSecret: cfg.ClientSecret, 74 | Endpoint: oauth2.Endpoint{ 75 | AuthURL: githubAuthorizeUrl, 76 | TokenURL: githubTokenUrl, 77 | }, 78 | RedirectURL: redirectUrl, 79 | Scopes: scopes, 80 | } 81 | 82 | r := mux.NewRouter() 83 | r.HandleFunc("/", HomeHandler) 84 | r.HandleFunc("/start", StartHandler) 85 | r.HandleFunc("/auth-callback", AuthCallbackHandler) 86 | r.HandleFunc("/destroy-session", SessionDestroyHandler) 87 | 88 | http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) 89 | http.Handle("/", r) 90 | 91 | listenAddr := ":8080" 92 | 93 | envPort := os.Getenv("PORT") 94 | if len(envPort) > 0 { 95 | listenAddr = ":" + envPort 96 | } 97 | 98 | log.Printf("attempting listen on %s", listenAddr) 99 | log.Fatalln(http.ListenAndServe(listenAddr, nil)) 100 | } 101 | -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Lato', sans-serif; 3 | } 4 | 5 | .current-user .avatar-thumbnail { 6 | width: 100px; 7 | height: 100px; 8 | } -------------------------------------------------------------------------------- /templates/home.html: -------------------------------------------------------------------------------- 1 | {{ define "title"}}Example{{ end }} 2 | {{ define "content" }} 3 | 9 |
10 | {{ if .github_user }} 11 |

Logged in as {{ .github_user.Name }}

12 | ... 13 |

Data

14 | 19 | 20 | {{ else }} 21 |

Not logged in as anyone

22 | {{ end }} 23 |
24 | 25 | 26 | {{ end }} -------------------------------------------------------------------------------- /templates/layout.html: -------------------------------------------------------------------------------- 1 | {{ define "base" }} 2 | 3 | 4 | 5 | 6 | {{template "title" . }} 7 | 8 | 9 |
10 |

golang-github-oauth-example

11 |
12 |
13 | {{ template "content" . }} 14 |
15 | 16 | 17 | {{ end }} --------------------------------------------------------------------------------