├── .travis.yml ├── README.md ├── go.mod ├── go.sum ├── main.go └── server.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.11.x 5 | - 1.12.x 6 | 7 | script: 8 | - go build ./... -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Go Oauth2 Tutorial 2 | =================== 3 | 4 | This repository contains code from the Go Oauth2 Tutorial on [TutorialEdge](https://tutorialedge.net/golang/go-oauth2-tutorial/) 5 | 6 | Go from zero to hundred as a Go developer with [TutorialEdge](https://tutorialedge.net/courses) for as low as $9 a month and $99 a year 7 | 8 | 9 | You'll find courses on Building Production Ready RESTful APIs, Testing, WebAssembly and many more, plus free tutorials on the [TutorialEdge Youtube Channel](https://www.youtube.com/c/tutorialedge) 10 | 11 | 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tutorialedge/go-oauth-tutorial 2 | 3 | require ( 4 | github.com/go-session/session v3.1.2+incompatible 5 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a 6 | gopkg.in/oauth2.v3 v3.9.5 7 | ) 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 5 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 6 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 7 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 8 | github.com/gavv/httpexpect v0.0.0-20180803094507-bdde30871313/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= 9 | github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA= 10 | github.com/go-session/session v3.1.2+incompatible h1:yStchEObKg4nk2F7JGE7KoFIrA/1Y078peagMWcrncg= 11 | github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0= 12 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 14 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 15 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 16 | github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= 17 | github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 18 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= 19 | github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 20 | github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 21 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 22 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 23 | github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= 24 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 25 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 26 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 27 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 28 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 29 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 30 | github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= 31 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 32 | github.com/tidwall/btree v0.0.0-20170113224114-9876f1454cf0 h1:QnyrPZZvPmR0AtJCxxfCtI1qN+fYpKTKJ/5opWmZ34k= 33 | github.com/tidwall/btree v0.0.0-20170113224114-9876f1454cf0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= 34 | github.com/tidwall/buntdb v1.0.0 h1:urIJqQ8OR9fibXXtFSu8sR5arMqZK8ZnNq22yWl+A+8= 35 | github.com/tidwall/buntdb v1.0.0/go.mod h1:Y39xhcDW10WlyYXeLgGftXVbjtM0QP+/kpz8xl9cbzE= 36 | github.com/tidwall/gjson v1.1.3 h1:u4mspaByxY+Qk4U1QYYVzGFI8qxN/3jtEV0ZDb2vRic= 37 | github.com/tidwall/gjson v1.1.3/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA= 38 | github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE= 39 | github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= 40 | github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= 41 | github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= 42 | github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2KoJQD9cTQ6dyP2co9q4yzmT9FZo= 43 | github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao= 44 | github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE= 45 | github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ= 46 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 47 | github.com/valyala/fasthttp v1.0.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= 48 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= 49 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 50 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 51 | github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= 52 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= 53 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 54 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 55 | github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= 56 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 57 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 58 | golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 59 | golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 60 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= 61 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 62 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= 63 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 64 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 65 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 66 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 67 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 68 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 69 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 70 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 71 | gopkg.in/oauth2.v3 v3.9.5 h1:d8n7in49VM6d6g4THkyAwykoS5UuuP1+3JfBJM8DsNg= 72 | gopkg.in/oauth2.v3 v3.9.5/go.mod h1:nTG+m2PRcHR9jzGNrGdxSsUKz7vvwkqSlhFrstgZcRU= 73 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 74 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 75 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | 10 | "golang.org/x/oauth2" 11 | ) 12 | 13 | var ( 14 | config = oauth2.Config{ 15 | ClientID: "222222", 16 | ClientSecret: "22222222", 17 | Scopes: []string{"all"}, 18 | RedirectURL: "http://localhost:9094/oauth2", 19 | // This points to our Authorization Server 20 | // if our Client ID and Client Secret are valid 21 | // it will attempt to authorize our user 22 | Endpoint: oauth2.Endpoint{ 23 | AuthURL: "http://localhost:9096/authorize", 24 | TokenURL: "http://localhost:9096/token", 25 | }, 26 | } 27 | ) 28 | 29 | // Homepage 30 | func HomePage(w http.ResponseWriter, r *http.Request) { 31 | fmt.Println("Homepage Hit!") 32 | u := config.AuthCodeURL("xyz") 33 | http.Redirect(w, r, u, http.StatusFound) 34 | } 35 | 36 | // Authorize 37 | func Authorize(w http.ResponseWriter, r *http.Request) { 38 | r.ParseForm() 39 | state := r.Form.Get("state") 40 | if state != "xyz" { 41 | http.Error(w, "State invalid", http.StatusBadRequest) 42 | return 43 | } 44 | 45 | code := r.Form.Get("code") 46 | if code == "" { 47 | http.Error(w, "Code not found", http.StatusBadRequest) 48 | return 49 | } 50 | 51 | token, err := config.Exchange(context.Background(), code) 52 | if err != nil { 53 | http.Error(w, err.Error(), http.StatusInternalServerError) 54 | return 55 | } 56 | 57 | e := json.NewEncoder(w) 58 | e.SetIndent("", " ") 59 | e.Encode(*token) 60 | } 61 | 62 | func main() { 63 | 64 | // 1 - We attempt to hit our Homepage route 65 | // if we attempt to hit this unauthenticated, it 66 | // will automatically redirect to our Auth 67 | // server and prompt for login credentials 68 | http.HandleFunc("/", HomePage) 69 | 70 | // 2 - This displays our state, code and 71 | // token and expiry time that we get back 72 | // from our Authorization server 73 | http.HandleFunc("/oauth2", Authorize) 74 | 75 | // 3 - We start up our Client on port 9094 76 | log.Println("Client is running at 9094 port.") 77 | log.Fatal(http.ListenAndServe(":9094", nil)) 78 | } -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "net/url" 7 | "os" 8 | 9 | "github.com/go-session/session" 10 | "gopkg.in/oauth2.v3/errors" 11 | "gopkg.in/oauth2.v3/manage" 12 | "gopkg.in/oauth2.v3/models" 13 | "gopkg.in/oauth2.v3/server" 14 | "gopkg.in/oauth2.v3/store" 15 | ) 16 | 17 | func main() { 18 | manager := manage.NewDefaultManager() 19 | // token store 20 | manager.MustTokenStorage(store.NewMemoryTokenStore()) 21 | 22 | clientStore := store.NewClientStore() 23 | clientStore.Set("222222", &models.Client{ 24 | ID: "222222", 25 | Secret: "22222222", 26 | Domain: "http://localhost:9094", 27 | }) 28 | manager.MapClientStorage(clientStore) 29 | 30 | srv := server.NewServer(server.NewConfig(), manager) 31 | srv.SetUserAuthorizationHandler(userAuthorizeHandler) 32 | 33 | srv.SetInternalErrorHandler(func(err error) (re *errors.Response) { 34 | log.Println("Internal Error:", err.Error()) 35 | return 36 | }) 37 | 38 | srv.SetResponseErrorHandler(func(re *errors.Response) { 39 | log.Println("Response Error:", re.Error.Error()) 40 | }) 41 | 42 | http.HandleFunc("/login", loginHandler) 43 | http.HandleFunc("/auth", authHandler) 44 | 45 | http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) { 46 | err := srv.HandleAuthorizeRequest(w, r) 47 | if err != nil { 48 | http.Error(w, err.Error(), http.StatusBadRequest) 49 | } 50 | }) 51 | 52 | http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { 53 | err := srv.HandleTokenRequest(w, r) 54 | if err != nil { 55 | http.Error(w, err.Error(), http.StatusInternalServerError) 56 | } 57 | }) 58 | 59 | log.Println("Server is running at 9096 port.") 60 | log.Fatal(http.ListenAndServe(":9096", nil)) 61 | } 62 | 63 | func userAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string, err error) { 64 | store, err := session.Start(nil, w, r) 65 | if err != nil { 66 | return 67 | } 68 | 69 | uid, ok := store.Get("UserID") 70 | if !ok { 71 | if r.Form == nil { 72 | r.ParseForm() 73 | } 74 | store.Set("ReturnUri", r.Form) 75 | store.Save() 76 | 77 | w.Header().Set("Location", "/login") 78 | w.WriteHeader(http.StatusFound) 79 | return 80 | } 81 | userID = uid.(string) 82 | store.Delete("UserID") 83 | store.Save() 84 | return 85 | } 86 | 87 | func loginHandler(w http.ResponseWriter, r *http.Request) { 88 | store, err := session.Start(nil, w, r) 89 | if err != nil { 90 | http.Error(w, err.Error(), http.StatusInternalServerError) 91 | return 92 | } 93 | 94 | if r.Method == "POST" { 95 | store.Set("LoggedInUserID", "000000") 96 | store.Save() 97 | 98 | w.Header().Set("Location", "/auth") 99 | w.WriteHeader(http.StatusFound) 100 | return 101 | } 102 | outputHTML(w, r, "static/login.html") 103 | } 104 | 105 | func authHandler(w http.ResponseWriter, r *http.Request) { 106 | store, err := session.Start(nil, w, r) 107 | if err != nil { 108 | http.Error(w, err.Error(), http.StatusInternalServerError) 109 | return 110 | } 111 | 112 | if _, ok := store.Get("LoggedInUserID"); !ok { 113 | w.Header().Set("Location", "/login") 114 | w.WriteHeader(http.StatusFound) 115 | return 116 | } 117 | 118 | if r.Method == "POST" { 119 | var form url.Values 120 | if v, ok := store.Get("ReturnUri"); ok { 121 | form = v.(url.Values) 122 | } 123 | u := new(url.URL) 124 | u.Path = "/authorize" 125 | u.RawQuery = form.Encode() 126 | w.Header().Set("Location", u.String()) 127 | w.WriteHeader(http.StatusFound) 128 | store.Delete("Form") 129 | 130 | if v, ok := store.Get("LoggedInUserID"); ok { 131 | store.Set("UserID", v) 132 | } 133 | store.Save() 134 | 135 | return 136 | } 137 | outputHTML(w, r, "static/auth.html") 138 | } 139 | 140 | func outputHTML(w http.ResponseWriter, req *http.Request, filename string) { 141 | file, err := os.Open(filename) 142 | if err != nil { 143 | http.Error(w, err.Error(), 500) 144 | return 145 | } 146 | defer file.Close() 147 | fi, _ := file.Stat() 148 | http.ServeContent(w, req, file.Name(), fi.ModTime(), file) 149 | } --------------------------------------------------------------------------------