Wait on in-flight OIDC discovery instead of returning 503
This commit is contained in:
+31
-14
@@ -22,10 +22,10 @@ type BearerOIDC struct {
|
||||
timeout time.Duration
|
||||
backoff time.Duration
|
||||
|
||||
mu sync.Mutex
|
||||
verifier *oidc.IDTokenVerifier
|
||||
discovering bool
|
||||
failedAt time.Time
|
||||
mu sync.Mutex
|
||||
verifier *oidc.IDTokenVerifier
|
||||
inflight chan struct{}
|
||||
failedAt time.Time
|
||||
}
|
||||
|
||||
var errIssuerUnavailable = errors.New("oidc issuer unavailable")
|
||||
@@ -39,18 +39,34 @@ func NewBearerOIDC(issuer string, audiences []string) *BearerOIDC {
|
||||
}
|
||||
|
||||
// getVerifier runs at most one discovery at a time without holding the lock
|
||||
// across network I/O; callers arriving mid-discovery or within the backoff fail fast.
|
||||
func (b *BearerOIDC) getVerifier() (*oidc.IDTokenVerifier, error) {
|
||||
// across network I/O; concurrent callers wait for it, and only callers within
|
||||
// the post-failure backoff fail fast.
|
||||
func (b *BearerOIDC) getVerifier(ctx context.Context) (*oidc.IDTokenVerifier, error) {
|
||||
b.mu.Lock()
|
||||
if b.verifier != nil || b.discovering || time.Since(b.failedAt) < b.backoff {
|
||||
v := b.verifier
|
||||
if v := b.verifier; v != nil {
|
||||
b.mu.Unlock()
|
||||
if v == nil {
|
||||
return nil, errIssuerUnavailable
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
b.discovering = true
|
||||
if done := b.inflight; done != nil {
|
||||
b.mu.Unlock()
|
||||
select {
|
||||
case <-done:
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if b.verifier == nil {
|
||||
return nil, errIssuerUnavailable
|
||||
}
|
||||
return b.verifier, nil
|
||||
}
|
||||
if time.Since(b.failedAt) < b.backoff {
|
||||
b.mu.Unlock()
|
||||
return nil, errIssuerUnavailable
|
||||
}
|
||||
done := make(chan struct{})
|
||||
b.inflight = done
|
||||
b.mu.Unlock()
|
||||
|
||||
// The provider keeps this context and client for JWKS refreshes, so the
|
||||
@@ -60,7 +76,8 @@ func (b *BearerOIDC) getVerifier() (*oidc.IDTokenVerifier, error) {
|
||||
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.discovering = false
|
||||
b.inflight = nil
|
||||
defer close(done)
|
||||
if err != nil {
|
||||
b.failedAt = time.Now()
|
||||
slog.Error("oidc discovery failed", "issuer", b.issuer, "error", err)
|
||||
@@ -83,7 +100,7 @@ func (b *BearerOIDC) Require(next http.Handler) http.Handler {
|
||||
unauthorized(w, "missing bearer token")
|
||||
return
|
||||
}
|
||||
v, err := b.getVerifier()
|
||||
v, err := b.getVerifier(r.Context())
|
||||
if err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
|
||||
Reference in New Issue
Block a user