Skip to main content

Go OAuth2 Server

Shenzhen, China

Github Repository

Coding along Build your own OAuth2 Server in Go.

Client -- Request Access Token --> OAuth2 Server (requires `client_id` & `client_secret`)
OAuth2 Server -- Grant Access Token --> Client
Client -- API Call --> Resource Server (requires `access_token`)
Resource Server -- Data Response --> Client
go mod init go-oauth2

Client Credentials Grant Flow Based Server

Resource Server

Provide an API that you want to protect using OAuth2:

package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/protected", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Secret Information in Need for Protection"))
	})

	log.Fatal(http.ListenAndServe(":9096", nil))

Go OAuth2 Server

Protecting the Route

Validate the access token given with the request:

func validateToken(f http.HandlerFunc, srv *server.Server) http.HandlerFunc {
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
      _, err := srv.ValidationBearerToken(r)
      if err != nil {
         http.Error(w, err.Error(), http.StatusBadRequest)
         return
      }

      f.ServeHTTP(w, r)
   })
}

Adding the middleware in front of the route I want to protect:

http.HandleFunc("/protected", validateToken(func(w http.ResponseWriter, r *http.Request) {
   w.Write([]byte("Secret Information in Need for Protection"))
}, srv))

Go OAuth2 Server

Authorization Server

Routing

  • /credentials for issuing client credentials (client_id and client_secret)
http.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) {
   //  create random client id
   clientId := uuid.New().String()[:8]
   //  create random client secret
   clientSecret := uuid.New().String()[:8]
   //  save both in client store
   err := clientStore.Set(clientId, &models.Client{
      ID:     clientId,
      Secret: clientSecret,
      // scoped to resource url
      Domain: "http://localhost:9094",
   })
   if err != nil {
      fmt.Println(err.Error())
   }

   w.Header().Set("Content-Type", "application/json")
   json.NewEncoder(w).Encode(map[string]string{"CLIENT_ID": clientId, "CLIENT_SECRET": clientSecret})
})

http://localhost:9096/credentials

Go OAuth2 Server

On the credentials route I am now receiving random values for the Client ID and Secret:

  1. client_id= 9f61b47b
  2. client_secret= 3d113393
  • /token to issue token with client credentials
http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
   srv.HandleTokenRequest(w, r)
})

http://localhost:9096/token?grant\_type=client\_credentials&client\_id=YOUR\_CLIENT\_ID&client\_secret=YOUR\_CLIENT\_SECRET&scope=all

Go OAuth2 Server

Here I am getting the Access Token with an expiration time that I can use to access my resource:

  1. access_token = SXH18FJHMI2GIP4GL_XTYQ

I can now use my access token to unlock the protected route:

http://localhost:9096/protected?access\_token=YOUR\_ACCESS\_TOKEN

Go OAuth2 Server

Complete Code

package main

import (
   "encoding/json"
   "fmt"
   "github.com/google/uuid"
   "gopkg.in/oauth2.v3/models"
   "log"
   "net/http"
   "time"

   "gopkg.in/oauth2.v3/errors"
   "gopkg.in/oauth2.v3/manage"
   "gopkg.in/oauth2.v3/server"
   "gopkg.in/oauth2.v3/store"
)

func main() {
   manager := manage.NewDefaultManager()
   manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)

   // token memory store
   manager.MustTokenStorage(store.NewMemoryTokenStore())

   // client memory store
   clientStore := store.NewClientStore()
   
   manager.MapClientStorage(clientStore)

   srv := server.NewDefaultServer(manager)
   srv.SetAllowGetAccessRequest(true)
   srv.SetClientInfoHandler(server.ClientFormHandler)
   manager.SetRefreshTokenCfg(manage.DefaultRefreshTokenCfg)

   srv.SetInternalErrorHandler(func(err error) (re *errors.Response) {
      log.Println("Internal Error:", err.Error())
      return
   })

   srv.SetResponseErrorHandler(func(re *errors.Response) {
      log.Println("Response Error:", re.Error.Error())
   })

   http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
      srv.HandleTokenRequest(w, r)
   })

   http.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) {
      clientId := uuid.New().String()[:8]
      clientSecret := uuid.New().String()[:8]
      err := clientStore.Set(clientId, &models.Client{
         ID:     clientId,
         Secret: clientSecret,
         Domain: "http://localhost:9094",
      })
      if err != nil {
         fmt.Println(err.Error())
      }

      w.Header().Set("Content-Type", "application/json")
      json.NewEncoder(w).Encode(map[string]string{"CLIENT_ID": clientId, "CLIENT_SECRET": clientSecret})
   })

   http.HandleFunc("/protected", validateToken(func(w http.ResponseWriter, r *http.Request) {
      w.Write([]byte("Secret Information in Need for Protection"))
   }, srv))

   log.Fatal(http.ListenAndServe(":9096", nil))
}

func validateToken(f http.HandlerFunc, srv *server.Server) http.HandlerFunc {
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
      _, err := srv.ValidationBearerToken(r)
      if err != nil {
         http.Error(w, err.Error(), http.StatusBadRequest)
         return
      }

      f.ServeHTTP(w, r)
   })
}