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)
})
}