-
Notifications
You must be signed in to change notification settings - Fork 0
/
jwk_client.go
127 lines (104 loc) · 2.82 KB
/
jwk_client.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
package auth0
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"strings"
"sync"
"gopkg.in/go-jose/go-jose.v2"
)
var (
ErrInvalidContentType = errors.New("should have a JSON content type for JWKS endpoint")
ErrInvalidAlgorithm = errors.New("algorithm is invalid")
)
type JWKClientOptions struct {
URI string
Client *http.Client
}
type JWKS struct {
Keys []jose.JSONWebKey `json:"keys"`
}
type JWKClient struct {
keyCacher KeyCacher
mu sync.Mutex
options JWKClientOptions
extractor RequestTokenExtractor
}
// NewJWKClient creates a new JWKClient instance from the
// provided options.
func NewJWKClient(options JWKClientOptions, extractor RequestTokenExtractor) *JWKClient {
return NewJWKClientWithCache(options, extractor, nil)
}
// NewJWKClientWithCache creates a new JWKClient instance from the
// provided options and a custom keycacher interface.
// Passing nil to keyCacher will create a persistent key cacher
func NewJWKClientWithCache(options JWKClientOptions, extractor RequestTokenExtractor, keyCacher KeyCacher) *JWKClient {
if extractor == nil {
extractor = RequestTokenExtractorFunc(FromHeader)
}
if keyCacher == nil {
keyCacher = newMemoryPersistentKeyCacher()
}
if options.Client == nil {
options.Client = http.DefaultClient
}
return &JWKClient{
keyCacher: keyCacher,
options: options,
extractor: extractor,
}
}
// GetKey returns the key associated with the provided ID.
func (j *JWKClient) GetKey(ID string) (jose.JSONWebKey, error) {
j.mu.Lock()
defer j.mu.Unlock()
searchedKey, err := j.keyCacher.Get(ID)
if err != nil {
keys, err := j.downloadKeys()
if err != nil {
return jose.JSONWebKey{}, err
}
addedKey, err := j.keyCacher.Add(ID, keys)
if err != nil {
return jose.JSONWebKey{}, err
}
return *addedKey, nil
}
return *searchedKey, nil
}
func (j *JWKClient) downloadKeys() ([]jose.JSONWebKey, error) {
req, err := http.NewRequest("GET", j.options.URI, new(bytes.Buffer))
if err != nil {
return []jose.JSONWebKey{}, err
}
resp, err := j.options.Client.Do(req)
if err != nil {
return []jose.JSONWebKey{}, err
}
defer resp.Body.Close()
if contentH := resp.Header.Get("Content-Type"); !strings.HasPrefix(contentH, "application/json") {
return []jose.JSONWebKey{}, ErrInvalidContentType
}
var jwks = JWKS{}
err = json.NewDecoder(resp.Body).Decode(&jwks)
if err != nil {
return []jose.JSONWebKey{}, err
}
if len(jwks.Keys) < 1 {
return []jose.JSONWebKey{}, ErrNoKeyFound
}
return jwks.Keys, nil
}
// GetSecret implements the GetSecret method of the SecretProvider interface.
func (j *JWKClient) GetSecret(r *http.Request) (interface{}, error) {
token, err := j.extractor.Extract(r)
if err != nil {
return nil, err
}
if len(token.Headers) < 1 {
return nil, ErrNoJWTHeaders
}
header := token.Headers[0]
return j.GetKey(header.KeyID)
}