Documentation ¶
Overview ¶
Package jwtv provides convenience routines to extract and validate a JWT and add middleware to various web server frameworks.
Example (Unwrapped) ¶
Example_unwrapped provides an example where the HTTP request's JWT is validated inside select routes
jv, _ := NewJWTValidator() mux := http.NewServeMux() mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // validate the JWT is signed, not expired, et.al. claims, err := jv.Validate(r) if err != nil { // JWT failed verification w.WriteHeader(http.StatusUnauthorized) return } if claims == nil { // no JWT was sent on request w.WriteHeader(http.StatusUnauthorized) return } // you must verify the claims if !claims.VerifyScope("foo", true) { // the claim does not have permission to perform this function w.WriteHeader(http.StatusForbidden) _, _ = w.Write([]byte("scope foo required")) return } if !claims.VerifyAudience("audience-1", true) { // the claim is not a member of audience-1 so cannot perform this function w.WriteHeader(http.StatusForbidden) _, _ = w.Write([]byte("aud audience-1 required")) return } })) // start the server, do the rest ...
Output:
Index ¶
- Constants
- func NewSharedFetcher(ctx context.Context) *jwk.AutoRefresh
- type Claims
- type Discoverer
- type JWTEndpoints
- type JWTValidator
- func (jv *JWTValidator) DiscoverEndpoints(oidcBaseURL string) error
- func (jv *JWTValidator) GetClaims(r *http.Request) *Claims
- func (jv *JWTValidator) Middleware(next http.Handler) http.Handler
- func (jv *JWTValidator) ParseIfAPIGWValidated(r *http.Request) (*Claims, error)
- func (jv *JWTValidator) ParseTokenWithoutValidation(token string) (*Claims, error)
- func (jv *JWTValidator) ParseWithoutValidation(r *http.Request) (*Claims, error)
- func (jv *JWTValidator) Validate(r *http.Request) (*Claims, error)
- func (jv *JWTValidator) ValidateToken(token string) (*Claims, error)
- type OIDCConfiguration
- type TokenInfo
- type ValidatorContextKey
- type ValidatorOption
- func OptionDiscoverJWKSCertsURI(idpHREF string) ValidatorOption
- func OptionEnableOnlineValidation() ValidatorOption
- func OptionSetJWKSFetcher(fetcher *jwk.AutoRefresh) ValidatorOption
- func OptionSetJWKSWellKnownURI(uri string) ValidatorOption
- func OptionSetPublicKey(pemOrFile string) ValidatorOption
- type WellKnownIssuer
Examples ¶
Constants ¶
const ( // OIDCWellKnownURI is the OIDC standard URI to discover configuration. To use in Keycloak, prepend the Keycloak HREF `https://{keycloak-addr}/auth/realms/{realm}/protocol` OIDCWellKnownURI = "/.well-known/openid-configuration" // JWKSWellKnowCertURI default JWKS URI of JWT issuer to get signing public keys. Only use if this is for the IdentityServer4 issuer. JWKSWellKnowCertURI = OIDCWellKnownURI + "/jwks" )
const ( TokenTypeNone = iota TokenTypeBearer TokenTypeBasicAuth )
const ( TokenLocHeader = iota TokenLocQuery TokenLocCookie )
const ClaimsContextKey = "claims"
Variables ¶
This section is empty.
Functions ¶
func NewSharedFetcher ¶
func NewSharedFetcher(ctx context.Context) *jwk.AutoRefresh
NewSharedFetcher a convenience to return a jwk.AutoRefresh key fetcher and can be used with OptionSetJWKSFetcher().
Types ¶
type Claims ¶
type Claims struct { // MapClaims the embedded jwt.MapClaims one can access in order to index as a map[string]interface{} jwt.MapClaims }
Claims embedded jwt.MapClaims with additional functionality. This type supplies all methods available to jwt.MapClaims, however, it cannot be indexed directly. The Golang way of extending a type.
func NewMapClaims ¶
NewMapClaims creates a subclass-like of jwt.MapClaims having all of its functionality including additions. You just can't index on it as a map; You'd need to use Claims.MapClaims directly for that.
func (*Claims) Get ¶
Get since you cannot index Claims, you need this to get a key value from the embedded map.
func (*Claims) GetString ¶
GetString get a key as a string. If the key exists, but is not a string, an empty string is returned.
func (*Claims) VerifyScope ¶
VerifyScope Compares the scope claim against cmp. If required is false, this method will return true if the value matches or is unset.
This function normalizes the ambiguous return types by various OIDC providers. Ex:
- IdentityServer4 presents: "scope": ["cps.app.client", "dng-filter.api.app", "kwscorer.api.app"]
- Keycloak presents: "scope": "cps.app.client dng-filter.api.app kwscorer.api.app"
Example ¶
ExampleClaims_VerifyScope demonstrate that Claims.VerifyScope() can handle differing formats from various JWT issuers.
// OIDC returns standard claims that include a scope claimsScopeSpaceSep := Claims{ MapClaims: jwt.MapClaims{ "scope": "one two three", }, } if !claimsScopeSpaceSep.VerifyScope("one", true) { return } // OIDC returns a different format claimsScopeArray := Claims{ MapClaims: jwt.MapClaims{ "scope": []string{"one", "two", "three"}, }, } // VerifyScope is able to handle the different format of scope if !claimsScopeArray.VerifyScope("one", true) { return }
Output:
type Discoverer ¶
type Discoverer struct {
// contains filtered or unexported fields
}
type JWTEndpoints ¶
type JWTEndpoints struct { Issuer string AuthorizationEndpoint string `json:"authorization_endpoint"` TokenEndpoint string `json:"token_endpoint"` IntrospectionEndpoint string `json:"introspection_endpoint"` // https://datatracker.ietf.org/doc/html/rfc7662 UserinfoEndpoint string `json:"userinfo_endpoint"` EndSessionEndpoint string `json:"end_session_endpoint"` JwksURI string `json:"jwks_uri"` RegistrationEndpoint string `json:"registration_endpoint"` RevocationEndpoint string `json:"revocation_endpoint"` // https://datatracker.ietf.org/doc/html/rfc7009 }
func DiscoverOidcEndpoints ¶
func DiscoverOidcEndpoints(hclient *http.Client, oidcBaseURL string) (*JWTEndpoints, error)
DiscoverOidcEndpoints calls the OIDC server's endpoint to discover all other endpoints provided by the server
type JWTValidator ¶
type JWTValidator struct { // JwksURI endpoint of JWT issuer to get signing public keys. // This may represent: // * a full HREF to the OIDC's well-known endpoint // * or an endpoint URI used in combination with the JWT's `iss` claim HREF // When empty at validation time, it will use 'iss' claim to lookup a predefined endpoint. JwksURI string // PublicKey specifies the public key to be used to verify all JWTs. This being defined supersedes the need to lookup the key from JwksURI. PublicKey interface{} // OnlineValidation if true, the OIDC server will be asked to validate the token. Otherwise, offline validation is done by checking the token signature against the OIDC server's public key. // WARN: if true, this always causes an extra connection to the OIDC server to validate the token. OnlineValidation bool // OIDCEndpoints if doing discovery against the OIDC server, this will be a map of idpURL to it's discovered endpoints OIDCEndpoints map[string]*JWTEndpoints // contains filtered or unexported fields }
JWTValidator the instance of the JWT validator
func NewJWTValidator ¶
func NewJWTValidator(opts ...ValidatorOption) (*JWTValidator, error)
NewJWTValidator create an instance of the validator for JWTs from a single issuer (e.g. OIDC).
The reason for the single issuer restriction is that the validator needs to know the JWKS endpoint to download keys. The 'iss' claim cannot be relied upon as there is no standard. JWT issuers may specify only the base URL of the IDP and not include the JWKS endpoint. Furthermore, there is no standard for the path to the JWKS endpoint.
With respect to the above, a JWKS fetcher can be shared by multiple validators when specifying OptionSetJWKSFetcher for `opts`.
Other options include OptionSetJWKSFetcher, OptionSetPublicKey
Example ¶
var jv *JWTValidator // validator will reference the JWT's 'iss' claim to determine the well-known JWKS endpoint to verify signing jv, _ = NewJWTValidator() // force the JWKS endpoint to claims["iss"] + "/well-known/jwks" jv, _ = NewJWTValidator( OptionSetJWKSWellKnownURI("/well-known/jwks"), ) // force the JWKS endpoint to the one specified jv, _ = NewJWTValidator( OptionSetJWKSWellKnownURI("https://login.local/well-known/jwks"), ) _ = jv
Output:
func (*JWTValidator) DiscoverEndpoints ¶
func (jv *JWTValidator) DiscoverEndpoints(oidcBaseURL string) error
func (*JWTValidator) GetClaims ¶
func (jv *JWTValidator) GetClaims(r *http.Request) *Claims
GetClaims returns the claims from a validated JWT of an HTTP request which is set during the Middleware handler.
One would call this from handlers that require authorization. For those handlers, if nil is returned from this call, you should respond with a 401 because a JWT was not present in the request. In the case when a JWT is present but does not validate, the Middleware handler would have already responded 401 and your handler would not be called.
When non-nil is returned, it is the responsibility of the caller to inspect and validate the individual claims.
func (*JWTValidator) Middleware ¶
func (jv *JWTValidator) Middleware(next http.Handler) http.Handler
Middleware wraps the HTTP handler 'next' to validate a JWT on the request. It validates a JWT from the header and responds with 401 if not valid. Otherwise, the claims from the JWT are added to the request context for later handlers to access via GetClaims().
WARN: This DOES NOT validate individual claims as that is the responsibility of the caller after getting results from GetClaims().
Example ¶
ExampleJWTValidator_Middleware provides an example where all routes automatically validate the request's JWT
jv, _ := NewJWTValidator() mux := http.NewServeMux() // wrap this endpoint with a JWT validator middleware mux.Handle("/getClients", jv.Middleware(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { // the middleware has successfully verified the JWT's integrity, thus all we do is validate the claims claims := jv.GetClaims(r) // you must verify the claims if !claims.VerifyScope("foo", true) { // the claim does not have permission to perform this function w.WriteHeader(http.StatusForbidden) _, _ = w.Write([]byte("scope foo required")) return } if !claims.VerifyAudience("audience-1", true) { // the claim is not a member of audience-1 so cannot perform this function w.WriteHeader(http.StatusForbidden) _, _ = w.Write([]byte("aud audience-1 required")) return } }, ))) // start the server, do the rest ...
Output:
func (*JWTValidator) ParseIfAPIGWValidated ¶
func (jv *JWTValidator) ParseIfAPIGWValidated(r *http.Request) (*Claims, error)
ParseIfAPIGWValidated Use this method to get the claims from the JWT if the request could have been routed through the APIGW. If it did come through APIGW, then don't verify the JWT signature (saving a network call) because APIGW already did that. Otherwise, it validates the JWT signature against the issuing OIDC server (cached).
WARNING: Only use this method if access to your service is locked behind APIGW.
func (*JWTValidator) ParseTokenWithoutValidation ¶
func (jv *JWTValidator) ParseTokenWithoutValidation(token string) (*Claims, error)
ParseTokenWithoutValidation parse the JWT token and return the claims. Does not validate the token signature.
See also ParseWithoutValidation() for the same functionality but taking the token from an HTTP request.
func (*JWTValidator) ParseWithoutValidation ¶
func (jv *JWTValidator) ParseWithoutValidation(r *http.Request) (*Claims, error)
ParseWithoutValidation parses the JWT token from an HTTP request and return the claims. Useful only if upstream has already validated the token.
WARNING: THIS DOES NOT VALIDATE THE TOKEN!
See also ParseTokenWithoutValidation().
func (*JWTValidator) Validate ¶
func (jv *JWTValidator) Validate(r *http.Request) (*Claims, error)
Validate validates an HTTP request's JWT token. The token may come from a cookie named 'access_token', a query param named 'access-token', or from the 'Authorization' header. If one exists and can be validated, this returns the claims from the token. If validation fails, this returns an error. If no JWT is present in the request, this returns nil for the claims and a nil error.
See also ValidateToken().
func (*JWTValidator) ValidateToken ¶
func (jv *JWTValidator) ValidateToken(token string) (*Claims, error)
ValidateToken validates a JWT token and returns claims.
See also Validate() to validate a token from an HTTP request.
type OIDCConfiguration ¶
type OIDCConfiguration struct { Issuer string `json:"issuer"` AuthURL string `json:"authorization_endpoint"` TokenURL string `json:"token_endpoint"` JWKSURL string `json:"jwks_uri"` UserInfoURL string `json:"userinfo_endpoint"` }
TODO this subsumed by discover.go OIDCConfiguration from response to IDP's /.well-known/openid-configuration
type TokenInfo ¶
type TokenInfo struct {
// contains filtered or unexported fields
}
TokenInfo defines how the JWT was found in an HTTP request
type ValidatorContextKey ¶
type ValidatorContextKey string
type ValidatorOption ¶
type ValidatorOption func(validator *JWTValidator) error
ValidatorOption signature for an option to NewJWTValidator
func OptionDiscoverJWKSCertsURI ¶
func OptionDiscoverJWKSCertsURI(idpHREF string) ValidatorOption
OptionDiscoverJWKSCertsURI is a NewJWTValidator Functional Option to call any IDP's OIDC discovery endpoint to look up the JWKS URI.
func OptionEnableOnlineValidation ¶
func OptionEnableOnlineValidation() ValidatorOption
OptionEnableOnlineValidation use the issuing OIDC server to validate each token. The only value in setting this is when you cannot tolerate a token that has not yet expired but has been revoked by some other service. For instance, on session logout, the token may be revoked by the OIDC server.
With short validity periods on tokens, the risk that a user logs off and someone gains access to their token to use afterwards to call an API service.
func OptionSetJWKSFetcher ¶
func OptionSetJWKSFetcher(fetcher *jwk.AutoRefresh) ValidatorOption
OptionSetJWKSFetcher is a NewJWTValidator Functional Option that sets the shared JWK key fetcher which may be used by other validators. The 'fetcher' argument can be acquired from a call to NewSharedFetcher().
The use case for this option began with APIGW whose config could possibly declare the same IDP for different proxy services. Thus, we'd like to share the JWK cache globally.
func OptionSetJWKSWellKnownURI ¶
func OptionSetJWKSWellKnownURI(uri string) ValidatorOption
OptionSetJWKSWellKnownURI is a NewJWTValidator Functional Option to set the endpoint URI of the JWT's issuer so that public keys can be acquired to validate a JWT. This may specify a full HREF to the OIDC's endpoint. Otherwise, it is expected that this is a URI and will be appended to the JWT claims `iss` (issuer) HREF to form the full URL to acquire JWKS keys from an OIDC server.
The default is JWKSWellKnowCertURI.
Use OptionDiscoverJWKSCertsURI() for the preferred method.
func OptionSetPublicKey ¶
func OptionSetPublicKey(pemOrFile string) ValidatorOption
OptionSetPublicKey is a NewJWTValidator Functional Option to specify the RSA public key to use to validate JWTs and is only useful when you know the JWT signer uses only one public key. The pemOrFile argument may be PEM data or a path to a PEM public key or certificate file.
This supersedes OptionSetJWKSWellKnownURI().
type WellKnownIssuer ¶
type WellKnownIssuer struct { // IssRE a regex to match a potential JWT 'iss' claim IssRE *regexp.Regexp // CertURI the URI to be appended to the JWT 'iss' claim to form the well-known cert endpoint CertURI string }
WellKnownIssuers struct to define well-known issuers JWKS certs URIs used to form JWKS certs endpoint