mirror of
				https://github.com/therootcompany/keypairs
				synced 2025-11-03 23:02:47 +00:00 
			
		
		
		
	add generic http handler for JWKs URL
This commit is contained in:
		
							parent
							
								
									8469f35bf7
								
							
						
					
					
						commit
						f542314cea
					
				
							
								
								
									
										117
									
								
								keydist/keydist.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								keydist/keydist.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					package keydist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						keypairs "github.com/big-squid/go-keypairs"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DefaultExpiresIn is 3 days
 | 
				
			||||||
 | 
					var DefaultExpiresIn = 72 * time.Hour
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// JWKsPath is "/.well-known/jwks.json" (Auth0 spec)
 | 
				
			||||||
 | 
					const JWKsPath = "/.well-known/jwks.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// jwksURL is ".well-known/jwks.json" (Auth0 spec)
 | 
				
			||||||
 | 
					var jwksURL, _ = url.Parse(".well-known/jwks.json")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OIDCPath is "/.well-known/openid-configuration" (OIDC spec)
 | 
				
			||||||
 | 
					const OIDCPath = "/.well-known/openid-configuration"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// oidcURL is ".well-known/openid-configuration" (OIDC spec)
 | 
				
			||||||
 | 
					var oidcURL, _ = url.Parse(".well-known/openid-configuration")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// for convenience
 | 
				
			||||||
 | 
					var notime time.Duration
 | 
				
			||||||
 | 
					var never = time.Time{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Middleware holds your public keys and has http handler methods for OIDC and Auth0 JWKs
 | 
				
			||||||
 | 
					type Middleware struct {
 | 
				
			||||||
 | 
						BaseURL   *url.URL
 | 
				
			||||||
 | 
						Keys      []keypairs.PublicKey
 | 
				
			||||||
 | 
						ExpiresIn time.Duration
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// General Note:
 | 
				
			||||||
 | 
					// Some frameworks don't properly handle the trailing ;charset=utf-8
 | 
				
			||||||
 | 
					// for Content-Type, and it doesn't add practical benefit, so we omit it
 | 
				
			||||||
 | 
					// (JSON _is_ utf-8, per spec, already).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Handler
 | 
				
			||||||
 | 
					func (m *Middleware) Handler(w http.ResponseWriter, r *http.Request) bool {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if strings.HasSuffix(r.URL.Path, jwksURL.Path) {
 | 
				
			||||||
 | 
							m.WellKnownJWKs(w, r)
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if strings.HasSuffix(r.URL.Path, oidcURL.Path) {
 | 
				
			||||||
 | 
							m.WellKnownOIDC(w, r)
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WellKnownOIDC serves a minimal OIDC config for the purpose of distributing JWKs
 | 
				
			||||||
 | 
					// if you need something more powerful, do it yourself.
 | 
				
			||||||
 | 
					// (but feel free to copy the code here)
 | 
				
			||||||
 | 
					func (m *Middleware) WellKnownOIDC(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						var baseURL url.URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Use a defined BaseURL, or an implicit one
 | 
				
			||||||
 | 
						if nil != m.BaseURL {
 | 
				
			||||||
 | 
							baseURL = *m.BaseURL
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							baseURL = *r.URL
 | 
				
			||||||
 | 
							baseURL.Host = r.Host
 | 
				
			||||||
 | 
							baseURL.Path = strings.TrimSuffix(baseURL.Path, oidcURL.Path)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// avoiding with correctly handling trailing vs non-trailing '/'
 | 
				
			||||||
 | 
						u := baseURL.ResolveReference(jwksURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json")
 | 
				
			||||||
 | 
						w.Write([]byte(fmt.Sprintf(`{ "issuer": %q, "jwks_uri": %q }`, baseURL, u)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WellKnownJWKs serves a JSON array of keys, no fluff
 | 
				
			||||||
 | 
					func (m *Middleware) WellKnownJWKs(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						// Use either the user-supplied key expiration or our own default
 | 
				
			||||||
 | 
						s := m.ExpiresIn
 | 
				
			||||||
 | 
						if notime == s {
 | 
				
			||||||
 | 
							s = DefaultExpiresIn
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						exp := time.Now().Add(s)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						jwks := marshalJWKs(m.Keys, exp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json")
 | 
				
			||||||
 | 
						w.Write([]byte(fmt.Sprintf(`{"keys":[%s]}`, strings.Join(jwks, ","))))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func marshalJWKs(keys []keypairs.PublicKey, exp2 time.Time) []string {
 | 
				
			||||||
 | 
						jwks := make([]string, 0, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := range keys {
 | 
				
			||||||
 | 
							key := keys[i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// if the key itself has an expiry, let that override
 | 
				
			||||||
 | 
							exp := key.ExpiresAt()
 | 
				
			||||||
 | 
							if never == exp {
 | 
				
			||||||
 | 
								// otherwise use our default
 | 
				
			||||||
 | 
								exp = exp2
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Note that you don't have to embed `iss` in the JWK because the client
 | 
				
			||||||
 | 
							// already has that info by virtue of getting to it in the first place.
 | 
				
			||||||
 | 
							jwk := string(keypairs.MarshalJWKPublicKey(key, exp))
 | 
				
			||||||
 | 
							jwks = append(jwks, jwk)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return jwks
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -41,6 +41,7 @@ type PublicKey interface {
 | 
				
			|||||||
	Thumbprint() string
 | 
						Thumbprint() string
 | 
				
			||||||
	KeyID() string
 | 
						KeyID() string
 | 
				
			||||||
	Key() crypto.PublicKey
 | 
						Key() crypto.PublicKey
 | 
				
			||||||
 | 
						ExpiresAt() time.Time
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ECPublicKey struct {
 | 
					type ECPublicKey struct {
 | 
				
			||||||
@ -67,6 +68,9 @@ func (p *ECPublicKey) Key() crypto.PublicKey {
 | 
				
			|||||||
func (p *ECPublicKey) ExpireAt(t time.Time) {
 | 
					func (p *ECPublicKey) ExpireAt(t time.Time) {
 | 
				
			||||||
	p.Expiry = t
 | 
						p.Expiry = t
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					func (p *ECPublicKey) ExpiresAt() time.Time {
 | 
				
			||||||
 | 
						return p.Expiry
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (p *RSAPublicKey) Thumbprint() string {
 | 
					func (p *RSAPublicKey) Thumbprint() string {
 | 
				
			||||||
	return ThumbprintUntypedPublicKey(p.PublicKey)
 | 
						return ThumbprintUntypedPublicKey(p.PublicKey)
 | 
				
			||||||
@ -80,6 +84,9 @@ func (p *RSAPublicKey) Key() crypto.PublicKey {
 | 
				
			|||||||
func (p *RSAPublicKey) ExpireAt(t time.Time) {
 | 
					func (p *RSAPublicKey) ExpireAt(t time.Time) {
 | 
				
			||||||
	p.Expiry = t
 | 
						p.Expiry = t
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					func (p *RSAPublicKey) ExpiresAt() time.Time {
 | 
				
			||||||
 | 
						return p.Expiry
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewPublicKey wraps a crypto.PublicKey to make it typesafe.
 | 
					// NewPublicKey wraps a crypto.PublicKey to make it typesafe.
 | 
				
			||||||
func NewPublicKey(pub crypto.PublicKey, kid ...string) PublicKey {
 | 
					func NewPublicKey(pub crypto.PublicKey, kid ...string) PublicKey {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user