-
Notifications
You must be signed in to change notification settings - Fork 0
/
auth.go
131 lines (114 loc) · 3.86 KB
/
auth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package main
import (
"encoding/json"
"errors"
"log"
"net/http"
"time"
jwtmiddleware "github.com/auth0/go-jwt-middleware"
jwt "github.com/dgrijalva/jwt-go"
)
// jwks stores a jsonWebKeys (JWK) set
type jwks struct {
Keys []jsonWebKeys `json:"keys"`
}
// jsonWebKeys represents one key from a JWK set
type jsonWebKeys struct {
Kty string `json:"kty"`
Kid string `json:"kid"`
Use string `json:"use"`
N string `json:"n"`
E string `json:"e"`
}
// PEMCert is a public key (PEM) that has been fetched from an auth server.
// if the key is out of date or the key id doesn't match, a new key will be
// fetched
type PEMCert struct {
Cert string
Kid string
Expiry time.Time
}
// JWTAuthentication returns a new JWTMiddleware from the auth0 go-jwt-middleware package.
// the JWTMiddleware can be used with chi middleware using jwtAuthentication().Handler
func (config *config) JWTAuthentication() *jwtmiddleware.JWTMiddleware {
var err error
// get new certificate when server initially starts
// create a new middleware
// see https://auth0.com/docs/
jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
// Verify 'aud' claim
checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(config.AuthAudience, false)
if !checkAud {
return token, errors.New("invalid audience")
}
// Verify 'iss' claim
checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(config.AuthIssuer, false)
if !checkIss {
return token, errors.New("invalid issuer")
}
// check if we need a new certificate
if config.AuthCert.Cert == "" || config.AuthCert.Kid != token.Header["kid"] || config.AuthCert.Expiry.Before(time.Now()) {
config.mux.Lock()
config.AuthCert, err = config.GetCert(token)
config.mux.Unlock()
if err != nil {
log.Panic(err)
}
}
// Verify the token
config.mux.RLock()
result, err := jwt.ParseRSAPublicKeyFromPEM([]byte(config.AuthCert.Cert))
config.mux.RUnlock()
if err != nil {
log.Panic(err)
}
return result, nil
},
SigningMethod: jwt.SigningMethodRS256,
})
return jwtMiddleware
}
// GetCert makes a request to the jwks endpoint and returns a public key certificate
// original code from from auth0.com/docs/
func (config *config) GetCert(token *jwt.Token) (PEMCert, error) {
// create a new PEM certificate `newCert`.
// it will not be returned unless we successfully populate it.
// if function returns an error, the existing `cert` certificate will be returned.
newCert := PEMCert{}
// make a request to the JWKS endpoint specified in `host` above
log.Println("Fetching new JWKS from", config.AuthIssuer)
response, err := http.Get(config.AuthJWKSEndpoint)
if err != nil {
return config.AuthCert, err
}
defer response.Body.Close()
// decode response as a jwks with a set of keys
var jwks = jwks{}
err = json.NewDecoder(response.Body).Decode(&jwks)
if err != nil {
return config.AuthCert, err
}
// find a key matching the token.
// if this function is called with no token, only the first key is cached.
// if a future token requires a different kid (signing key was rotated), then the
// cached certificate will not match and the JWKS endpoint will be retried.
keys:
for k := range jwks.Keys {
if token == nil || token.Header["kid"] == jwks.Keys[k].Kid {
// set a new certificate and mark expiry for 24 hours from now
newCert.Cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].N + "\n-----END CERTIFICATE-----"
newCert.Kid = jwks.Keys[k].Kid
newCert.Expiry = time.Now().Add(24 * time.Hour)
log.Println("New auth certificate obtained.")
break keys
}
}
if newCert.Cert == "" {
err := errors.New("unable to find appropriate key")
// return previously cached cert.
// this may happen if user has a token signed by an old key that was rotated out.
return config.AuthCert, err
}
return newCert, nil
}