forked from auth0/go-jwt-middleware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
provider.go
152 lines (126 loc) · 4 KB
/
provider.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package jwks
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"sync"
"time"
"gopkg.in/square/go-jose.v2"
"github.com/lamlabs/go-jwt-middleware/v2/internal/oidc"
)
// Provider handles getting JWKS from the specified IssuerURL and exposes
// KeyFunc which adheres to the keyFunc signature that the Validator requires.
// Most likely you will want to use the CachingProvider as it handles
// getting and caching JWKS which can help reduce request time and potential
// rate limiting from your provider.
type Provider struct {
IssuerURL *url.URL // Required.
CustomJWKSURI *url.URL // Optional.
Client *http.Client
}
// ProviderOption is how options for the Provider are set up.
type ProviderOption func(*Provider)
// NewProvider builds and returns a new *Provider.
func NewProvider(issuerURL *url.URL, opts ...ProviderOption) *Provider {
p := &Provider{
IssuerURL: issuerURL,
Client: &http.Client{},
}
for _, opt := range opts {
opt(p)
}
return p
}
// WithCustomJWKSURI will set a custom JWKS URI on the *Provider and
// call this directly inside the keyFunc in order to fetch the JWKS,
// skipping the oidc.GetWellKnownEndpointsFromIssuerURL call.
func WithCustomJWKSURI(jwksURI *url.URL) ProviderOption {
return func(p *Provider) {
p.CustomJWKSURI = jwksURI
}
}
// WithCustomClient will set a custom *http.Client on the *Provider
func WithCustomClient(c *http.Client) ProviderOption {
return func(p *Provider) {
p.Client = c
}
}
// KeyFunc adheres to the keyFunc signature that the Validator requires.
// While it returns an interface to adhere to keyFunc, as long as the
// error is nil the type will be *jose.JSONWebKeySet.
func (p *Provider) KeyFunc(ctx context.Context) (interface{}, error) {
jwksURI := p.CustomJWKSURI
if jwksURI == nil {
wkEndpoints, err := oidc.GetWellKnownEndpointsFromIssuerURL(ctx, p.Client, *p.IssuerURL)
if err != nil {
return nil, err
}
jwksURI, err = url.Parse(wkEndpoints.JWKSURI)
if err != nil {
return nil, fmt.Errorf("could not parse JWKS URI from well known endpoints: %w", err)
}
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, jwksURI.String(), nil)
if err != nil {
return nil, fmt.Errorf("could not build request to get JWKS: %w", err)
}
response, err := p.Client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
var jwks jose.JSONWebKeySet
if err := json.NewDecoder(response.Body).Decode(&jwks); err != nil {
return nil, fmt.Errorf("could not decode jwks: %w", err)
}
return &jwks, nil
}
// CachingProvider handles getting JWKS from the specified IssuerURL
// and caching them for CacheTTL time. It exposes KeyFunc which adheres
// to the keyFunc signature that the Validator requires.
type CachingProvider struct {
*Provider
CacheTTL time.Duration
mu sync.Mutex
cache map[string]cachedJWKS
}
type cachedJWKS struct {
jwks *jose.JSONWebKeySet
expiresAt time.Time
}
// NewCachingProvider builds and returns a new CachingProvider.
// If cacheTTL is zero then a default value of 1 minute will be used.
func NewCachingProvider(issuerURL *url.URL, cacheTTL time.Duration, opts ...ProviderOption) *CachingProvider {
if cacheTTL == 0 {
cacheTTL = 1 * time.Minute
}
return &CachingProvider{
Provider: NewProvider(issuerURL, opts...),
CacheTTL: cacheTTL,
cache: map[string]cachedJWKS{},
}
}
// KeyFunc adheres to the keyFunc signature that the Validator requires.
// While it returns an interface to adhere to keyFunc, as long as the
// error is nil the type will be *jose.JSONWebKeySet.
func (c *CachingProvider) KeyFunc(ctx context.Context) (interface{}, error) {
c.mu.Lock()
defer c.mu.Unlock()
issuer := c.IssuerURL.Hostname()
if cached, ok := c.cache[issuer]; ok {
if !time.Now().After(cached.expiresAt) {
return cached.jwks, nil
}
}
jwks, err := c.Provider.KeyFunc(ctx)
if err != nil {
return nil, err
}
c.cache[issuer] = cachedJWKS{
jwks: jwks.(*jose.JSONWebKeySet),
expiresAt: time.Now().Add(c.CacheTTL),
}
return jwks, nil
}