@@ -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
1819type 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+
2236func 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