summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2022-05-10 18:19:34 -0600
committermo khan <mo@mokhan.ca>2022-05-10 18:19:36 -0600
commit4aee5f9b3e00bf0c94099f18d48fc71ae18d4519 (patch)
treeeedf1ef21e132427c16e8a0df9cf64bb0e56c7cf
complete auth0 guide
* https://auth0.com/docs/quickstart/backend/golang
-rw-r--r--.env2
-rw-r--r--go.mod14
-rw-r--r--go.sum15
-rw-r--r--main.go60
-rw-r--r--pkg/middleware/jwt.go72
5 files changed, 163 insertions, 0 deletions
diff --git a/.env b/.env
new file mode 100644
index 0000000..d742842
--- /dev/null
+++ b/.env
@@ -0,0 +1,2 @@
+AUTH0_DOMAIN='cmd0-dev.us.auth0.com'
+AUTH0_AUDIENCE='https://api.cmdzero.io'
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..f342ac8
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,14 @@
+module github.com/xlgmokha/api-auth0
+
+go 1.18
+
+require (
+ github.com/auth0/go-jwt-middleware/v2 v2.0.1
+ github.com/joho/godotenv v1.4.0
+)
+
+require (
+ github.com/pkg/errors v0.9.1 // indirect
+ golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
+ gopkg.in/square/go-jose.v2 v2.6.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..5ccd740
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,15 @@
+github.com/auth0/go-jwt-middleware/v2 v2.0.1 h1:zAgDKL7nsfVBFl31GGxsSXkhuRzYe1fVtJcO3aMSrFU=
+github.com/auth0/go-jwt-middleware/v2 v2.0.1/go.mod h1:kDt7JgUuDEp1VutfUmO4ZxBLL51vlNu/56oDfXc5E0Y=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
+github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
+golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
+gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..d8834ad
--- /dev/null
+++ b/main.go
@@ -0,0 +1,60 @@
+package main
+
+import (
+ "log"
+ "net/http"
+
+ jwtmiddleware "github.com/auth0/go-jwt-middleware/v2"
+ "github.com/auth0/go-jwt-middleware/v2/validator"
+ "github.com/joho/godotenv"
+ "github.com/xlgmokha/api-auth0/pkg/middleware"
+)
+
+func main() {
+ if err := godotenv.Load(); err != nil {
+ log.Fatal(err)
+ }
+
+ router := http.NewServeMux()
+
+ router.Handle("/api/public", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"message":"public"}`))
+ }))
+
+ router.Handle("/api/private", middleware.EnsureValidToken()(
+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
+ w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
+ w.Header().Set("Access-Control-Allow-Headers", "Authorization")
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"message":"private"}`))
+ }),
+ ))
+
+ router.Handle("/api/private-scoped", middleware.EnsureValidToken()(
+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
+ w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
+ w.Header().Set("Access-Control-Allow-Headers", "Authorization")
+
+ w.Header().Set("Content-Type", "application/json")
+
+ token := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims)
+ claims := token.CustomClaims.(*middleware.CustomClaims)
+ if !claims.HasScope("read:messages") {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte(`{"message":"insufficient scope."}`))
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"message":"private-scoped"}`))
+ }),
+ ))
+
+ log.Fatal(http.ListenAndServe("0.0.0.0:3000", router))
+}
diff --git a/pkg/middleware/jwt.go b/pkg/middleware/jwt.go
new file mode 100644
index 0000000..a58eff6
--- /dev/null
+++ b/pkg/middleware/jwt.go
@@ -0,0 +1,72 @@
+package middleware
+
+import (
+ "context"
+ "log"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+ "time"
+
+ jwtmiddleware "github.com/auth0/go-jwt-middleware/v2"
+ "github.com/auth0/go-jwt-middleware/v2/jwks"
+ "github.com/auth0/go-jwt-middleware/v2/validator"
+)
+
+type CustomClaims struct {
+ Scope string `json:"scope"`
+}
+
+func (c CustomClaims) Validate(ctx context.Context) error {
+ return nil
+}
+
+func (c CustomClaims) HasScope(expectedScope string) bool {
+ result := strings.Split(c.Scope, " ")
+ for i := range result {
+ if result[i] == expectedScope {
+ return true
+ }
+ }
+ return false
+}
+
+func EnsureValidToken() func(next http.Handler) http.Handler {
+ issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ provider := jwks.NewCachingProvider(issuerURL, 5*time.Minute)
+ jwtValidator, err := validator.New(
+ provider.KeyFunc,
+ validator.RS256,
+ issuerURL.String(),
+ []string{os.Getenv("AUTH0_AUDIENCE")},
+ validator.WithCustomClaims(
+ func() validator.CustomClaims {
+ return &CustomClaims{}
+ },
+ ),
+ validator.WithAllowedClockSkew(time.Minute),
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ errorHandler := func(w http.ResponseWriter, r *http.Request, err error) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusUnauthorized)
+ w.Write([]byte(`{"message":"Failed to validate JWT."}`))
+ }
+
+ middleware := jwtmiddleware.New(
+ jwtValidator.ValidateToken,
+ jwtmiddleware.WithErrorHandler(errorHandler),
+ )
+
+ return func(next http.Handler) http.Handler {
+ return middleware.CheckJWT(next)
+ }
+}