Skip to content

Commit 407e051

Browse files
authored
Merge pull request #18 from auth0-samples/use-go-jwt-middleware
Use go jwt middleware to decode and verify tokens
2 parents cedd42d + a8299dc commit 407e051

2 files changed

Lines changed: 126 additions & 85 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.DS_Store
22
.env
3+
src

01-Authorization-RS256/main.go

Lines changed: 125 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -5,133 +5,173 @@ import (
55
"fmt"
66
"net/http"
77
"strings"
8+
"errors"
9+
"log"
10+
"os"
811

9-
auth0 "github.com/auth0-community/go-auth0"
12+
"github.com/codegangsta/negroni"
13+
"github.com/auth0/go-jwt-middleware"
14+
"github.com/dgrijalva/jwt-go"
1015
"github.com/gorilla/mux"
11-
jose "gopkg.in/square/go-jose.v2"
12-
jwt "gopkg.in/square/go-jose.v2/jwt"
1316
"github.com/joho/godotenv"
14-
"log"
15-
"os"
1617
)
1718

1819
type Response struct {
1920
Message string `json:"message"`
2021
}
2122

23+
type Jwks struct {
24+
Keys []JSONWebKeys `json:"keys"`
25+
}
26+
27+
type JSONWebKeys struct {
28+
Kty string `json:"kty"`
29+
Kid string `json:"kid"`
30+
Use string `json:"use"`
31+
N string `json:"n"`
32+
E string `json:"e"`
33+
X5c []string `json:"x5c"`
34+
}
35+
2236
func main() {
2337

2438
err := godotenv.Load()
2539
if err != nil {
2640
log.Print("Error loading .env file")
2741
}
2842

43+
jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options {
44+
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
45+
// Verify 'aud' claim
46+
aud := os.Getenv("AUTH0_AUDIENCE")
47+
checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false)
48+
if !checkAud {
49+
return token, errors.New("Invalid audience.")
50+
}
51+
// Verify 'iss' claim
52+
iss := "https://" + os.Getenv("AUTH0_DOMAIN") + "/"
53+
checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false)
54+
if !checkIss {
55+
return token, errors.New("Invalid issuer.")
56+
}
57+
58+
cert, err := getPemCert(token)
59+
if err != nil {
60+
panic(err.Error())
61+
}
62+
63+
result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert))
64+
return result, nil
65+
},
66+
SigningMethod: jwt.SigningMethodRS256,
67+
})
68+
2969
r := mux.NewRouter()
3070

3171
// This route is always accessible
3272
r.Handle("/api/public", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
33-
response := Response{
34-
Message: "Hello from a public endpoint! You don't need to be authenticated to see this.",
35-
}
36-
37-
w.Header().Set("Content-Type", "application/json")
38-
w.WriteHeader(http.StatusOK)
39-
json.NewEncoder(w).Encode(response)
73+
message := "Hello from a public endpoint! You don't need to be authenticated to see this."
74+
responseJSON(message, w, http.StatusOK)
4075
}))
4176

4277
// This route is only accessible if the user has a valid access_token
43-
// We are wrapping the checkJwt middleware around the handler function which will check for a valid token.
44-
r.Handle("/api/private", checkJwt(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
45-
response := Response{
46-
Message: "Hello from a private endpoint! You need to be authenticated to see this.",
47-
}
48-
49-
w.Header().Set("Content-Type", "application/json")
50-
w.WriteHeader(http.StatusOK)
51-
json.NewEncoder(w).Encode(response)
52-
53-
})))
78+
// We are chaining the jwtmiddleware middleware into the negroni handler function which will check
79+
// for a valid token.
80+
r.Handle("/api/private", negroni.New(
81+
negroni.HandlerFunc(jwtMiddleware.HandlerWithNext),
82+
negroni.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
83+
message := "Hello from a private endpoint! You need to be authenticated to see this."
84+
responseJSON(message, w, http.StatusOK)
85+
}))))
5486

5587
// This route is only accessible if the user has a valid access_token with the read:messages scope
56-
// We are wrapping the checkJwt middleware around the handler function which will check for a
57-
// valid token and scope.
58-
r.Handle("/api/private-scoped", checkJwt(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
59-
// Ensure the token has the correct scope
60-
JWKS_URI := "https://" + os.Getenv("AUTH0_DOMAIN") + "/.well-known/jwks.json"
61-
client := auth0.NewJWKClient(auth0.JWKClientOptions{URI: JWKS_URI})
62-
aud := os.Getenv("AUTH0_AUDIENCE")
63-
audience := []string{aud}
64-
65-
var AUTH0_API_ISSUER = "https://" + os.Getenv("AUTH0_DOMAIN") + "/"
66-
configuration := auth0.NewConfiguration(client, audience, AUTH0_API_ISSUER, jose.RS256)
67-
validator := auth0.NewValidator(configuration)
68-
token, _ := validator.ValidateRequest(r)
69-
result := checkScope(r, validator, token)
70-
if result == true {
71-
response := Response{
72-
Message: "Hello from a private endpoint! You need to be authenticated and have a scope of read:messages to see this.",
73-
}
74-
w.Header().Set("Content-Type", "application/json")
75-
w.WriteHeader(http.StatusOK)
76-
json.NewEncoder(w).Encode(response)
77-
} else {
78-
response := Response{
79-
Message: "You do not have the read:messages scope.",
88+
// We are chaining the jwtmiddleware middleware into the negroni handler function which will check
89+
// for a valid token and and scope.
90+
r.Handle("/api/private-scoped", negroni.New(
91+
negroni.HandlerFunc(jwtMiddleware.HandlerWithNext),
92+
negroni.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
93+
authHeaderParts := strings.Split(r.Header.Get("Authorization"), " ")
94+
token := authHeaderParts[1]
95+
96+
hasScope := checkScope("read:messages", token)
97+
98+
if !hasScope {
99+
message := "Insufficient scope."
100+
responseJSON(message, w, http.StatusForbidden)
101+
return
80102
}
81-
w.Header().Set("Content-Type", "application/json")
82-
w.WriteHeader(http.StatusForbidden)
83-
json.NewEncoder(w).Encode(response)
103+
message := "Hello from a private endpoint! You need to be authenticated to see this."
104+
responseJSON(message, w, http.StatusOK)
105+
}))))
84106

85-
}
107+
http.Handle("/", r)
108+
fmt.Println("Listening on http://localhost:3010")
109+
http.ListenAndServe("0.0.0.0:3010", nil)
110+
}
86111

87-
})))
88112

89-
fmt.Println("Listening on http://localhost:3010")
90-
http.ListenAndServe("0.0.0.0:3010", r)
113+
type CustomClaims struct {
114+
Scope string `json:"scope"`
115+
jwt.StandardClaims
91116
}
92117

93-
func checkJwt(h http.Handler) http.Handler {
94-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
95-
JWKS_URI := "https://" + os.Getenv("AUTH0_DOMAIN") + "/.well-known/jwks.json"
96-
client := auth0.NewJWKClient(auth0.JWKClientOptions{URI: JWKS_URI})
97-
aud := os.Getenv("AUTH0_AUDIENCE")
98-
audience := []string{aud}
118+
func checkScope(scope string, tokenString string) bool {
119+
token, _ := jwt.ParseWithClaims(tokenString, &CustomClaims{}, nil)
120+
121+
claims, _ := token.Claims.(*CustomClaims)
99122

100-
var AUTH0_API_ISSUER = "https://" + os.Getenv("AUTH0_DOMAIN") + "/"
101-
configuration := auth0.NewConfiguration(client, audience, AUTH0_API_ISSUER, jose.RS256)
102-
validator := auth0.NewValidator(configuration)
123+
hasScope := false
124+
result := strings.Split(claims.Scope, " ")
125+
for i := range result {
126+
if result[i] == scope {
127+
hasScope = true
128+
}
129+
}
103130

104-
_, err := validator.ValidateRequest(r)
131+
return hasScope
132+
}
105133

106-
if err != nil {
107-
fmt.Println("Token is not valid or missing token")
134+
func getPemCert(token *jwt.Token) (string, error) {
135+
cert := ""
136+
resp, err := http.Get("https://" + os.Getenv("AUTH0_DOMAIN") + "/.well-known/jwks.json")
108137

109-
response := Response{
110-
Message: "Missing or invalid token.",
111-
}
138+
if err != nil {
139+
return cert, err
140+
}
141+
defer resp.Body.Close()
112142

113-
w.Header().Set("Content-Type", "application/json")
114-
w.WriteHeader(http.StatusUnauthorized)
115-
json.NewEncoder(w).Encode(response)
143+
var jwks = Jwks{}
144+
err = json.NewDecoder(resp.Body).Decode(&jwks)
116145

117-
} else {
118-
h.ServeHTTP(w, r)
146+
if err != nil {
147+
return cert, err
148+
}
149+
150+
x5c := jwks.Keys[0].X5c
151+
for k, v := range x5c {
152+
if token.Header["kid"] == jwks.Keys[k].Kid {
153+
cert = "-----BEGIN CERTIFICATE-----\n" + v + "\n-----END CERTIFICATE-----"
119154
}
120-
})
155+
}
156+
157+
if cert == "" {
158+
err := errors.New("Unable to find appropriate key.")
159+
return cert, err
160+
}
161+
162+
return cert, nil
121163
}
122164

123-
func checkScope(r *http.Request, validator *auth0.JWTValidator, token *jwt.JSONWebToken) bool {
124-
claims := map[string]interface{}{}
125-
err := validator.Claims(r, token, &claims)
165+
func responseJSON(message string, w http.ResponseWriter, statusCode int) {
166+
response := Response{message}
126167

168+
jsonResponse, err := json.Marshal(response)
127169
if err != nil {
128-
fmt.Println(err)
129-
return false
170+
http.Error(w, err.Error(), http.StatusInternalServerError)
171+
return
130172
}
131173

132-
if claims["scope"] != nil && strings.Contains(claims["scope"].(string), "read:messages") {
133-
return true
134-
} else {
135-
return false
136-
}
174+
w.Header().Set("Content-Type", "application/json")
175+
w.WriteHeader(statusCode)
176+
w.Write(jsonResponse)
137177
}

0 commit comments

Comments
 (0)