Go OAuth2 Server
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))
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))
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
On the credentials
route I am now receiving random values for the Client ID and Secret:
- client_id=
9f61b47b
- client_secret=
3d113393
/token
to issue token with client credentials
http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
srv.HandleTokenRequest(w, r)
})
Here I am getting the Access Token with an expiration time that I can use to access my resource:
- 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
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)
})
}