keypairs/keydist/keydist.go

118 lines
3.0 KiB
Go
Raw Normal View History

2019-02-28 22:31:10 +00:00
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
}