Documentation ¶
Overview ¶
Package jwt implements “JSON Web Token (JWT)” RFC 7519. Signatures only; no unsecured nor encrypted tokens.
Example (Extend) ¶
Demo use of a non-standard algorithm and custom JOSE heading.
package main import ( "crypto" _ "crypto/md5" // link into binary "encoding/json" "fmt" "github.com/pascaldekloe/jwt" ) // JWTHeaders defines custom JOSE heading for token production. var JWTHeaders = json.RawMessage(`{"lan": "XL9", "tcode": 102}`) func init() { // static algorithm registration jwt.HMACAlgs["HM5"] = crypto.MD5 } // Demo use of a non-standard algorithm and custom JOSE heading. func main() { // issue a JWT c := jwt.Claims{KeyID: "№1"} token, err := c.HMACSign("HM5", []byte("guest"), JWTHeaders) if err != nil { fmt.Println("sign error:", err) return } fmt.Println("token:", string(token)) // verify a JWT claims, err := jwt.HMACCheck(token, []byte("guest")) if err != nil { fmt.Println("check error:", err) return } fmt.Println("header:", string(claims.RawHeader)) }
Output: token: eyJhbGciOiJITTUiLCJraWQiOiLihJYxIiwibGFuIjoiWEw5IiwidGNvZGUiOjEwMn0.e30.8i8eLO5fHTv1ucdUWtBRMA header: {"alg":"HM5","kid":"№1","lan":"XL9","tcode":102}
Index ¶
- Constants
- Variables
- func BearerToken(h http.Header) (token string, err error)
- type AlgError
- type Claims
- func ECDSACheck(token []byte, key *ecdsa.PublicKey) (*Claims, error)
- func ECDSACheckHeader(r *http.Request, key *ecdsa.PublicKey) (*Claims, error)
- func EdDSACheck(token []byte, key ed25519.PublicKey) (*Claims, error)
- func EdDSACheckHeader(r *http.Request, key ed25519.PublicKey) (*Claims, error)
- func HMACCheck(token, secret []byte) (*Claims, error)
- func HMACCheckHeader(r *http.Request, secret []byte) (*Claims, error)
- func ParseWithoutCheck(token []byte) (*Claims, error)
- func RSACheck(token []byte, key *rsa.PublicKey) (*Claims, error)
- func RSACheckHeader(r *http.Request, key *rsa.PublicKey) (*Claims, error)
- func (c *Claims) ECDSASign(alg string, key *ecdsa.PrivateKey, extraHeaders ...json.RawMessage) (token []byte, err error)
- func (c *Claims) ECDSASignHeader(r *http.Request, alg string, key *ecdsa.PrivateKey) error
- func (c *Claims) EdDSASign(key ed25519.PrivateKey, extraHeaders ...json.RawMessage) (token []byte, err error)
- func (c *Claims) EdDSASignHeader(r *http.Request, key ed25519.PrivateKey) error
- func (c *Claims) FormatWithoutSign(alg string, extraHeaders ...json.RawMessage) (tokenWithoutSignature []byte, err error)
- func (c *Claims) HMACSign(alg string, secret []byte, extraHeaders ...json.RawMessage) (token []byte, err error)
- func (c *Claims) HMACSignHeader(r *http.Request, alg string, secret []byte) error
- func (c *Claims) Number(name string) (value float64, ok bool)
- func (c *Claims) RSASign(alg string, key *rsa.PrivateKey, extraHeaders ...json.RawMessage) (token []byte, err error)
- func (c *Claims) RSASignHeader(r *http.Request, alg string, key *rsa.PrivateKey) error
- func (c *Claims) String(name string) (value string, ok bool)
- type HMAC
- type Handler
- type KeyRegister
- func (keys *KeyRegister) Check(token []byte) (*Claims, error)
- func (keys *KeyRegister) CheckHeader(r *http.Request) (*Claims, error)
- func (keys *KeyRegister) LoadJWK(data []byte) (keysAdded int, err error)
- func (keys *KeyRegister) LoadPEM(text, password []byte) (keysAdded int, err error)
- func (keys *KeyRegister) PEM() ([]byte, error)
- type NumericTime
- type Registered
- Bugs
Examples ¶
Constants ¶
const ( EdDSA = "EdDSA" // EdDSA signature algorithms ES256 = "ES256" // ECDSA using P-256 and SHA-256 ES384 = "ES384" // ECDSA using P-384 and SHA-384 // Deprecated: Use ES384 instead. // “Go just implemented all specified curves from FIPS 186. If I were to // make that choice today we'd have only P-256 and P-384. // // In 12 years, they will either all be broken by quantum computers, or // P-384 and P-521 will both stand.” // — Filippo Valsorda ES512 = "ES512" // ECDSA using P-521 and SHA-512 HS256 = "HS256" // HMAC using SHA-256 HS384 = "HS384" // HMAC using SHA-384 HS512 = "HS512" // HMAC using SHA-512 PS256 = "PS256" // RSASSA-PSS using SHA-256 and MGF1 with SHA-256 PS384 = "PS384" // RSASSA-PSS using SHA-384 and MGF1 with SHA-384 PS512 = "PS512" // RSASSA-PSS using SHA-512 and MGF1 with SHA-512 RS256 = "RS256" // RSASSA-PKCS1-v1_5 using SHA-256 RS384 = "RS384" // RSASSA-PKCS1-v1_5 using SHA-384 RS512 = "RS512" // RSASSA-PKCS1-v1_5 using SHA-512 )
Algorithm Identification Tokens
const ErrUnsecured = AlgError("none")
ErrUnsecured signals a token without a signature, as described in RFC 7519, section 6.
const MIMEType = "application/jwt"
MIMEType is the IANA registered media type.
const OAuthURN = "urn:ietf:params:oauth:token-type:jwt"
OAuthURN is the IANA registered OAuth URI.
Variables ¶
var ( ECDSAAlgs = map[string]crypto.Hash{ ES256: crypto.SHA256, ES384: crypto.SHA384, ES512: crypto.SHA512, } HMACAlgs = map[string]crypto.Hash{ HS256: crypto.SHA256, HS384: crypto.SHA384, HS512: crypto.SHA512, } RSAAlgs = map[string]crypto.Hash{ PS256: crypto.SHA256, PS384: crypto.SHA384, PS512: crypto.SHA512, RS256: crypto.SHA256, RS384: crypto.SHA384, RS512: crypto.SHA512, } )
Algorithm support is configured with hash registrations. Any modifications should be made before first use to prevent data races in the Check and Sign functions, i.e., customise from either main or init.
var ErrNoHeader = errors.New("jwt: no HTTP authorization header")
ErrNoHeader signals an HTTP request without authorization.
var ErrSigMiss = errors.New("jwt: signature mismatch")
ErrSigMiss means the signature check failed.
var EvalCrit = func(token []byte, crit []string, header json.RawMessage) error { return fmt.Errorf("jwt: unsupported critical extension in JOSE header: %q", crit) }
EvalCrit is invoked by the Check functions for each token with one or more JOSE extensions. The crit slice has the JSON field names (for header) which “MUST be understood and processed” according to RFC 7515, subsection 4.1.11. “If any of the listed extension Header Parameters are not understood and supported by the recipient, then the JWS is invalid.” The respective Check function returns any error from EvalCrit as is.
Functions ¶
Types ¶
type AlgError ¶ added in v1.4.0
type AlgError string
AlgError signals that the specified algorithm is not in use.
type Claims ¶
type Claims struct { // Registered field values take precedence over Set. Registered // Set maps claims by name, for usecases beyond the Registered fields. // The Sign methods copy each non-zero Registered value into Set when // the map is not nil. The Check methods map claims in Set if the name // doesn't match any of the Registered, or if the data type won't fit. // Entries are treated conform the encoding/json package. // // bool, for JSON booleans // float64, for JSON numbers // string, for JSON strings // []interface{}, for JSON arrays // map[string]interface{}, for JSON objects // nil for JSON null // Set map[string]interface{} // Raw encoding as is within the token. This field is read-only. Raw json.RawMessage // RawHeader encoding as is within the token. This field is read-only. RawHeader json.RawMessage // “The "kid" (key ID) Header Parameter is a hint indicating which key // was used to secure the JWS. This parameter allows originators to // explicitly signal a change of key to recipients. The structure of the // "kid" value is unspecified. Its value MUST be a case-sensitive // string. Use of this Header Parameter is OPTIONAL.” // — “JSON Web Signature (JWS)” RFC 7515, subsection 4.1.4 KeyID string }
Claims are the (signed) statements of a JWT.
Example ¶
Note how the security model is flawed without any purpose claims. The bare minimum should include time constraints like Expires.
package main import ( "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "time" "github.com/pascaldekloe/jwt" ) func main() { var c jwt.Claims c.Issuer = "malory" c.Subject = "sterling" c.Audiences = []string{"armory"} // Approval is a custom claim element. type Approval struct { Name string `json:"name"` Count int `json:"count"` } c.Set = map[string]interface{}{ "approved": []Approval{{"RPG-7", 1}}, } // issue a JWT token, err := c.RSASign(jwt.RS256, RSAKey) if err != nil { fmt.Println("token creation failed on", err) return } // verify a JWT claims, err := jwt.RSACheck(token, &RSAKey.PublicKey) if err != nil { fmt.Println("credentials rejected:", err) return } err = claims.AcceptTemporal(time.Now(), time.Second) if err != nil { fmt.Println("credential constraints violated:", err) return } if !claims.AcceptAudience("armory") { fmt.Println("credentials not for armory:", claims.Audiences) return } fmt.Println(string(claims.Raw)) } // RSAPEM is a PKCS #1 form of RSAJWK. const RSAPEM = `-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd/wWJcyQoTbji9k0 l8W26mPddxHmfHQp+Vaw+4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL+yRT+SFd2lZS+pC gNMsD1W/YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb/7OMg0LOL+bSf63kpaSHSX ndS5z5rexMdbBYUsLA9e+KXBdQOS+UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uD Zlxvb3qCo5ZwKh9kG4LT6/I5IhlJH7aGhyxXFvUK+DWNmoudF8NAco9/h9iaGNj8 q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQIDAQABAoIBABKucaRpzQorw35S bEUAVx8dYXUdZOlJcHtiWQ+dC6V8ljxAHj/PLyzTveyI5QO/xkObCyjIL303l2cf UhPu2MFaJdjVzqACXuOrLot/eSFvxjvqVidTtAZExqFRJ9mylUVAoLvhowVWmC1O n95fZCXxTUtxNEG1Xcc7m0rtzJKs45J+N/V9DP1edYH6USyPSWGp6wuA+KgHRnKK Vf9GRx80JQY7nVNkL17eHoTWEwga+lwi0FEoW9Y7lDtWXYmKBWhUE+U8PGxlJf8f 40493HDw1WRQ/aSLoS4QTp3rn7gYgeHEvfJdkkf0UMhlknlo53M09EFPdadQ4TlU bjqKc50CgYEA4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH/5IB3jw3bcxGn6QLvnE tfdUdiYrqBdss1l58BQ3KhooKeQTa9AB0Hw/Py5PJdTJNPY8cQn7ouZ2KKDcmnPG BY5t7yLc1QlQ5xHdwW1VhvKn+nXqhJTBgIPgtldC+KDV5z+y2XDwGUcCgYEAuQPE fgmVtjL0Uyyx88GZFF1fOunH3+7cepKmtH4pxhtCoHqpWmT8YAmZxaewHgHAjLYs p1ZSe7zFYHj7C6ul7TjeLQeZD/YwD66t62wDmpe/HlB+TnBA+njbglfIsRLtXlnD zQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdcCgYAHAp9XcCSrn8wVkMVkKdb7 DOX4IKjzdahm+ctDAJN4O/y7OW5FKebvUjdAIt2GuoTZ71iTG+7F0F+lP88jtjP4 U4qe7VHoewl4MKOfXZKTe+YCS1XbNvfgwJ3Ltyl1OH9hWvu2yza7q+d5PCsDzqtm 27kxuvULVeya+TEdAB1ijQKBgQCH/3r6YrVH/uCWGy6bzV1nGNOdjKc9tmkfOJmN 54dxdixdpozCQ6U4OxZrsj3FcOhHBsqAHvX2uuYjagqvo3cOj1TRqNocX40omfCC Mx3bD1yPPf/6TI2XECva/ggqEY2mYzmIiA5LVVmc5nrybr+lssFKneeyxN2Wq93S 0iJMdQKBgCGHewxzoa1r8ZMD0LETNrToK423K377UCYqXfg5XMclbrjPbEC3YI1Z NqMtuhdBJqUnBi6tjKMF+34Xf0CUN8ncuXGO2CAYvO8PdyCixHX52ybaDjy1FtCE 6yUXjoKNXKvUm7MWGsAYH6f4IegOetN5NvmUMFStCSkh7ixZLkN1 -----END RSA PRIVATE KEY-----` var RSAKey = mustParseRSAKey(RSAPEM) func mustParseRSAKey(s string) *rsa.PrivateKey { block, _ := pem.Decode([]byte(s)) if block == nil { panic("no PEM block") } key, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { panic(err) } return key }
Output: {"approved":[{"name":"RPG-7","count":1}],"aud":["armory"],"iss":"malory","sub":"sterling"}
Example (ByName) ¶
Typed Claim Lookups
package main import ( "fmt" "time" "github.com/pascaldekloe/jwt" ) func main() { now := time.Unix(1537622794, 0) c := jwt.Claims{ Registered: jwt.Registered{ Issuer: "a", Subject: "b", Audiences: []string{"c"}, Expires: jwt.NewNumericTime(now.Add(time.Minute)), NotBefore: jwt.NewNumericTime(now.Add(-time.Second)), Issued: jwt.NewNumericTime(now), ID: "d", }, Set: map[string]interface{}{ "ext": "e", "nde": true, }, } for _, name := range []string{"iss", "sub", "aud", "exp", "nbf", "iat", "jti", "ext", "nde"} { if s, ok := c.String(name); ok { fmt.Printf("%q: %q\n", name, s) } if n, ok := c.Number(name); ok { fmt.Printf("%q: %0.f\n", name, n) } if b, ok := c.Set[name].(bool); ok { fmt.Printf("%q: %t\n", name, b) } } }
Output: "iss": "a" "sub": "b" "aud": "c" "exp": 1537622854 "nbf": 1537622793 "iat": 1537622794 "jti": "d" "ext": "e" "nde": true
func ECDSACheck ¶
ECDSACheck parses a JWT if, and only if, the signature checks out. The return is an AlgError when the algorithm is not in ECDSAAlgs. Use Valid to complete the verification.
func ECDSACheckHeader ¶
ECDSACheckHeader applies ECDSACheck on an HTTP request. Specifically it looks for a bearer token in the Authorization header.
func EdDSACheck ¶ added in v1.6.0
EdDSACheck parses a JWT if, and only if, the signature checks out. Use Valid to complete the verification.
func EdDSACheckHeader ¶ added in v1.6.0
EdDSACheckHeader applies EdDSACheck on an HTTP request. Specifically it looks for a bearer token in the Authorization header.
func HMACCheck ¶
HMACCheck parses a JWT if, and only if, the signature checks out. The return is an AlgError when the algorithm is not in HMACAlgs. Use Valid to complete the verification.
func HMACCheckHeader ¶
HMACCheckHeader applies HMACCheck on an HTTP request. Specifically it looks for a bearer token in the Authorization header.
func ParseWithoutCheck ¶ added in v1.6.0
ParseWithoutCheck skips the signature validation.
func RSACheck ¶
RSACheck parses a JWT if, and only if, the signature checks out. The return is an AlgError when the algorithm is not in RSAAlgs. Use Valid to complete the verification.
func RSACheckHeader ¶
RSACheckHeader applies RSACheck on an HTTP request. Specifically it looks for a bearer token in the Authorization header.
func (*Claims) ECDSASign ¶
func (c *Claims) ECDSASign(alg string, key *ecdsa.PrivateKey, extraHeaders ...json.RawMessage) (token []byte, err error)
ECDSASign updates the Raw fields and returns a new JWT. The return is an AlgError when alg is not in ECDSAAlgs. The caller must use the correct key for the respective algorithm (P-256 for ES256, P-384 for ES384 and P-521 for ES512) or risk malformed token production.
The JOSE header (content) can be extended with extraHeaders, in the form of JSON objects. Redundant and/or duplicate keys are applied as provided.
func (*Claims) ECDSASignHeader ¶
ECDSASignHeader applies ECDSASign on an HTTP request. Specifically it sets a bearer token in the Authorization header.
func (*Claims) EdDSASign ¶ added in v1.6.0
func (c *Claims) EdDSASign(key ed25519.PrivateKey, extraHeaders ...json.RawMessage) (token []byte, err error)
EdDSASign updates the Raw fields and returns a new JWT.
The JOSE header (content) can be extended with extraHeaders, in the form of JSON objects. Redundant and/or duplicate keys are applied as provided.
func (*Claims) EdDSASignHeader ¶ added in v1.6.0
EdDSASignHeader applies ECDSASign on an HTTP request. Specifically it sets a bearer token in the Authorization header.
func (*Claims) FormatWithoutSign ¶ added in v1.7.0
func (c *Claims) FormatWithoutSign(alg string, extraHeaders ...json.RawMessage) (tokenWithoutSignature []byte, err error)
FormatWithoutSign updates the Raw fields and returns a new JWT, with only the first two parts.
tokenWithoutSignature :≡ header-base64 '.' payload-base64 token :≡ tokenWithoutSignature '.' signature-base64
The JOSE header (content) can be extended with extraHeaders, in the form of JSON objects. Redundant and/or duplicate keys are applied as provided.
func (*Claims) HMACSign ¶
func (c *Claims) HMACSign(alg string, secret []byte, extraHeaders ...json.RawMessage) (token []byte, err error)
HMACSign updates the Raw fields and returns a new JWT. The return is an AlgError when alg is not in HMACAlgs.
The JOSE header (content) can be extended with extraHeaders, in the form of JSON objects. Redundant and/or duplicate keys are applied as provided.
func (*Claims) HMACSignHeader ¶
HMACSignHeader applies HMACSign on an HTTP request. Specifically it sets a bearer token in the Authorization header.
func (*Claims) Number ¶
Number returns the claim when present and if the representation is a JSON number. Note that null is not a number.
func (*Claims) RSASign ¶
func (c *Claims) RSASign(alg string, key *rsa.PrivateKey, extraHeaders ...json.RawMessage) (token []byte, err error)
RSASign updates the Raw fields and returns a new JWT. The return is an AlgError when alg is not in RSAAlgs.
The JOSE header (content) can be extended with extraHeaders, in the form of JSON objects. Redundant and/or duplicate keys are applied as provided.
func (*Claims) RSASignHeader ¶
RSASignHeader applies RSASign on an HTTP request. Specifically it sets a bearer token in the Authorization header.
type HMAC ¶ added in v1.10.0
type HMAC struct {
// contains filtered or unexported fields
}
HMAC is a reusable instance, optimized for high usage scenarios.
Multiple goroutines may invoke methods on an HMAC simultaneously.
func (*HMAC) Check ¶ added in v1.10.0
Check parses a JWT if, and only if, the signature checks out. The return is an AlgError when the algorithm does not match. Use Valid to complete the verification.
func (*HMAC) CheckHeader ¶ added in v1.10.0
CheckHeader applies Check on an HTTP request. Specifically it looks for a bearer token in the Authorization header.
type Handler ¶
type Handler struct { // Target is the secured service. Target http.Handler // Keys defines the trusted credentials. Keys *KeyRegister // HeaderBinding maps JWT claim names to HTTP header names. // All requests passed to Target have these headers set. In // case of failure the request is rejected with status code // 401 (Unauthorized) and a description. HeaderBinding map[string]string // HeaderPrefix is an optional constraint for JWT claim binding. // Any client headers that match the prefix are removed from the // request. HeaderPrefix string // ContextKey places the validated Claims in the context of // each respective request passed to Target when set. See // http.Request.Context and context.Context.Value. ContextKey interface{} // TemporalLeeway controls the tolerance with time constraints. TemporalLeeway time.Duration // When not nil, then Func is called after the JWT validation // succeeds and before any header bindings. Target is skipped // [request drop] when the return is false. // This feature may be used to further customise requests or // as a filter or as an extended http.HandlerFunc. Func func(http.ResponseWriter, *http.Request, *Claims) (pass bool) // Error sends a custom response. Nil defaults to http.Error. // The appropriate WWW-Authenticate value is already present. Error func(w http.ResponseWriter, error string, statusCode int) }
Handler protects an http.Handler with security enforcements. Requests are only passed to Target if the JWT checks out.
Example ¶
package main import ( "crypto/ed25519" "fmt" "io" "net/http" "net/http/httptest" "os" "github.com/pascaldekloe/jwt" ) func main() { // standard HTTP handler http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Hello %s!\n", req.Header.Get("X-Verified-Name")) fmt.Fprintf(w, "You are authorized as %s.\n", req.Header.Get("X-Verified-User")) }) // secure service configuration srv := httptest.NewTLSServer(&jwt.Handler{ Target: http.DefaultServeMux, Keys: &jwt.KeyRegister{EdDSAs: []ed25519.PublicKey{EdPublicKey}}, HeaderPrefix: "X-Verified-", HeaderBinding: map[string]string{ "sub": "X-Verified-User", // registered [standard] claim name "fn": "X-Verified-Name", // private [custom] claim name }, }) defer srv.Close() // call service req, _ := http.NewRequest("GET", srv.URL, nil) req.Header.Set("Authorization", "Bearer eyJhbGciOiJFZERTQSJ9.eyJmbiI6IkxhbmEgQW50aG9ueSBLYW5lIiwic3ViIjoibGFrYW5lIn0.B0DpTbticlRJN8y867gmylujJdfHRnnrFF_nTPkpYbVt9-1Ne1-YawzQxzOQXyZa7HwoU-Um8jOI_Fh8xubjAg") resp, _ := srv.Client().Do(req) fmt.Println("HTTP", resp.Status) io.Copy(os.Stdout, resp.Body) } // EdPublicKey is an example key from RFC 8037, appendix A.1. var EdPublicKey = ed25519.PublicKey{ 0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07, 0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07, 0x51, 0x1a, }
Output: HTTP 200 OK Hello Lana Anthony Kane! You are authorized as lakane.
Example (Context) ¶
Claims From Request Context
package main import ( "fmt" "net/http" "net/http/httptest" "github.com/pascaldekloe/jwt" ) func main() { const claimsKey = "verified-jwt" // secure service configuration h := &jwt.Handler{ Target: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { claims := req.Context().Value(claimsKey).(*jwt.Claims) if n, ok := claims.Number("deadline"); !ok { fmt.Fprintln(w, "no deadline") } else { fmt.Fprintln(w, "deadline at", (*jwt.NumericTime)(&n)) } }), Keys: &jwt.KeyRegister{Secrets: [][]byte{[]byte("killarcherdie")}}, ContextKey: claimsKey, } // call service req := httptest.NewRequest("GET", "/status", nil) req.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzM4NCJ9.eyJkZWFkbGluZSI6NjcxNTAwNzk5fQ.HS3mmHVfgP9EMpV4LLzagc6BB1P9J9Yh5TRA9DQHS4GeEejqMaBX0N4LAsMPgW0G") resp := httptest.NewRecorder() h.ServeHTTP(resp, req) fmt.Println("HTTP", resp.Code) fmt.Println(resp.Body) }
Output: HTTP 200 deadline at 1991-04-12T23:59:59Z
Example (Error) ¶
package main import ( "crypto/ecdsa" "crypto/x509" "encoding/pem" "fmt" "net/http" "net/http/httptest" "time" "github.com/pascaldekloe/jwt" ) func main() { // secure service configuration h := &jwt.Handler{ Target: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "My plan is to crowdsource a plan!") }), Keys: &jwt.KeyRegister{ECDSAs: []*ecdsa.PublicKey{&ECKey.PublicKey}}, // customise with JSON messages Error: func(w http.ResponseWriter, error string, statusCode int) { w.Header().Set("Content-Type", "application/json;charset=UTF-8") w.WriteHeader(statusCode) fmt.Fprintf(w, `{"msg": %q}`, error) }, } // call service with expired token req := httptest.NewRequest("GET", "/had-something-for-this", nil) var c jwt.Claims c.Expires = jwt.NewNumericTime(time.Now().Add(-time.Second)) if err := c.ECDSASignHeader(req, jwt.ES512, ECKey); err != nil { fmt.Println("sign error:", err) } resp := httptest.NewRecorder() h.ServeHTTP(resp, req) fmt.Println("HTTP", resp.Code) fmt.Println(resp.Header().Get("WWW-Authenticate")) fmt.Println(resp.Body) } // ECPEM is a PKCS #8 form of ECJWK. const ECPEM = `-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjpsQnnGQmL+YBIff H1136cspYG6+0iY7X1fCE9+E9LKhRANCAAR/zc4ncPbEXUGDy+5v20t7WAczNXvp 7xO6z248e9FURcfxRM0bvZt+hyzf7bnuufSzaV1uqQskrYpGIyiFiOWt -----END PRIVATE KEY-----` var ECKey = mustParseECKey(ECPEM) func mustParseECKey(s string) *ecdsa.PrivateKey { block, _ := pem.Decode([]byte(s)) if block == nil { panic("no PEM block") } key, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { panic(err) } return key.(*ecdsa.PrivateKey) }
Output: HTTP 401 Bearer error="invalid_token", error_description="jwt: expiration time [\"exp\"] passed" {"msg": "jwt: expiration time [\"exp\"] passed"}
Example (Filter) ¶
Func As A Request Filter
package main import ( "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "net/http" "net/http/httptest" "github.com/pascaldekloe/jwt" ) func main() { // secure service configuration h := &jwt.Handler{ Target: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Elaborate voicemail hoax!") }), Keys: &jwt.KeyRegister{RSAs: []*rsa.PublicKey{&RSAKey.PublicKey}}, Func: func(w http.ResponseWriter, req *http.Request, claims *jwt.Claims) (pass bool) { if claims.Subject != "marcher" { http.Error(w, "Ring, ring!", http.StatusServiceUnavailable) return false } return true }, } // call service req := httptest.NewRequest("GET", "/urgent", nil) req.Header.Set("Authorization", "Bearer eyJhbGciOiJQUzI1NiJ9.e30.KSfpjI7WFxGjI0t7NEqPFpcOkEQfR9YiK0nRxDqA7Eoz3X2Af4MhDTgHy4tTXwNBCpW0K-fjMfRG0E34nnsFWsUqFLuMq-geftUUf9aA7E2jrfcZUgi5-FlvOCk8P-iAbqfX0rTIyEBQ21huv75NdYnlfg_2RNd8YqhtxyqTPEjlb0_oLigGEYM6T0eySjNv8V-W2w97HBABHjEaP9aNqj2q_ZB5qERJ-qKP--JYGNx-rTaydFnDAIyWgbRIG2X9IaCRWKe-R8Qz3t76OkZIm7lXiDuYk7aMmfhtSrDL80bpTWGqyQ9AOxAKOTVNTRoTr3Z5cGxrg6B6p3fs4thvFw") resp := httptest.NewRecorder() h.ServeHTTP(resp, req) fmt.Println("HTTP", resp.Code) fmt.Println(resp.Body) } // RSAPEM is a PKCS #1 form of RSAJWK. const RSAPEM = `-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd/wWJcyQoTbji9k0 l8W26mPddxHmfHQp+Vaw+4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL+yRT+SFd2lZS+pC gNMsD1W/YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb/7OMg0LOL+bSf63kpaSHSX ndS5z5rexMdbBYUsLA9e+KXBdQOS+UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uD Zlxvb3qCo5ZwKh9kG4LT6/I5IhlJH7aGhyxXFvUK+DWNmoudF8NAco9/h9iaGNj8 q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQIDAQABAoIBABKucaRpzQorw35S bEUAVx8dYXUdZOlJcHtiWQ+dC6V8ljxAHj/PLyzTveyI5QO/xkObCyjIL303l2cf UhPu2MFaJdjVzqACXuOrLot/eSFvxjvqVidTtAZExqFRJ9mylUVAoLvhowVWmC1O n95fZCXxTUtxNEG1Xcc7m0rtzJKs45J+N/V9DP1edYH6USyPSWGp6wuA+KgHRnKK Vf9GRx80JQY7nVNkL17eHoTWEwga+lwi0FEoW9Y7lDtWXYmKBWhUE+U8PGxlJf8f 40493HDw1WRQ/aSLoS4QTp3rn7gYgeHEvfJdkkf0UMhlknlo53M09EFPdadQ4TlU bjqKc50CgYEA4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH/5IB3jw3bcxGn6QLvnE tfdUdiYrqBdss1l58BQ3KhooKeQTa9AB0Hw/Py5PJdTJNPY8cQn7ouZ2KKDcmnPG BY5t7yLc1QlQ5xHdwW1VhvKn+nXqhJTBgIPgtldC+KDV5z+y2XDwGUcCgYEAuQPE fgmVtjL0Uyyx88GZFF1fOunH3+7cepKmtH4pxhtCoHqpWmT8YAmZxaewHgHAjLYs p1ZSe7zFYHj7C6ul7TjeLQeZD/YwD66t62wDmpe/HlB+TnBA+njbglfIsRLtXlnD zQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdcCgYAHAp9XcCSrn8wVkMVkKdb7 DOX4IKjzdahm+ctDAJN4O/y7OW5FKebvUjdAIt2GuoTZ71iTG+7F0F+lP88jtjP4 U4qe7VHoewl4MKOfXZKTe+YCS1XbNvfgwJ3Ltyl1OH9hWvu2yza7q+d5PCsDzqtm 27kxuvULVeya+TEdAB1ijQKBgQCH/3r6YrVH/uCWGy6bzV1nGNOdjKc9tmkfOJmN 54dxdixdpozCQ6U4OxZrsj3FcOhHBsqAHvX2uuYjagqvo3cOj1TRqNocX40omfCC Mx3bD1yPPf/6TI2XECva/ggqEY2mYzmIiA5LVVmc5nrybr+lssFKneeyxN2Wq93S 0iJMdQKBgCGHewxzoa1r8ZMD0LETNrToK423K377UCYqXfg5XMclbrjPbEC3YI1Z NqMtuhdBJqUnBi6tjKMF+34Xf0CUN8ncuXGO2CAYvO8PdyCixHX52ybaDjy1FtCE 6yUXjoKNXKvUm7MWGsAYH6f4IegOetN5NvmUMFStCSkh7ixZLkN1 -----END RSA PRIVATE KEY-----` var RSAKey = mustParseRSAKey(RSAPEM) func mustParseRSAKey(s string) *rsa.PrivateKey { block, _ := pem.Decode([]byte(s)) if block == nil { panic("no PEM block") } key, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { panic(err) } return key }
Output: HTTP 503 Ring, ring!
type KeyRegister ¶
type KeyRegister struct { ECDSAs []*ecdsa.PublicKey // ECDSA credentials EdDSAs []ed25519.PublicKey // EdDSA credentials RSAs []*rsa.PublicKey // RSA credentials HMACs []*HMAC // HMAC credentials Secrets [][]byte // HMAC credentials // Optional key identification. See Claims.KeyID for details. // Non-empty strings match the respective key or secret by index. ECDSAIDs []string // ECDSAs key ID mapping EdDSAIDs []string // EdDSA key ID mapping RSAIDs []string // RSAs key ID mapping HMACIDs []string // HMACs key ID mapping SecretIDs []string // Secrets key ID mapping }
KeyRegister is a collection of recognized credentials.
func (*KeyRegister) Check ¶
func (keys *KeyRegister) Check(token []byte) (*Claims, error)
Check parses a JWT if, and only if, the signature checks out. Use Claims.Valid to complete the verification.
func (*KeyRegister) CheckHeader ¶
func (keys *KeyRegister) CheckHeader(r *http.Request) (*Claims, error)
CheckHeader applies KeyRegister.Check on an HTTP request. Specifically it looks for a bearer token in the Authorization header.
func (*KeyRegister) LoadJWK ¶ added in v1.6.0
func (keys *KeyRegister) LoadJWK(data []byte) (keysAdded int, err error)
LoadJWK adds keys from the JSON data to the register, including the key ID, a.k.a "kid", when present. If the object has a "keys" attribute, then data is read as a JWKS (JSON Web Key Set). Otherwise, data is read as a single JWK.
Example ¶
JWKS With Key IDs
package main import ( "fmt" "github.com/pascaldekloe/jwt" ) func main() { const json = `{ "keys": [ {"kty": "OKP", "crv":"Ed25519", "kid": "kazak", "d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}, {"kty":"oct", "k":"a29mdGE", "kid": "good old"} ] }` var keys jwt.KeyRegister _, err := keys.LoadJWK([]byte(json)) if err != nil { fmt.Println("load error:", err) } fmt.Printf("got %d EdDSA %q", len(keys.EdDSAs), keys.EdDSAIDs) fmt.Printf(" + %d secret %q", len(keys.Secrets), keys.SecretIDs) }
Output: got 1 EdDSA ["kazak"] + 1 secret ["good old"]
func (*KeyRegister) LoadPEM ¶
func (keys *KeyRegister) LoadPEM(text, password []byte) (keysAdded int, err error)
LoadPEM scans text for PEM-encoded keys. Each occurrence found is then added to the register. Extraction works with certificates, public keys and private keys. PEM encryption is enforced with a non-empty password to ensure security when ordered.
Example (Encrypted) ¶
PEM With Password Protection
package main import ( "fmt" "github.com/pascaldekloe/jwt" ) func main() { const pem = `Keep it private! ✨ -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,65789712555A3E9FECD1D5E235B97B0C o0Dz8S6QjGVq59yQdlakuKkoO0jKDN0PDu2L05ZLXwBQSGdIbRXtAOBRCNEME0V1 IF9pM6uRU7tqFoVneNTHD3XySJG8AHrTPSKC3Xw31pjEolMfoNDBAu1bYR6XxM2X oDu2UNVB9vd/3b4bwTH9q5ISWdCVhS/ky0lC9lHXman/F/7MsemiVVCQ4XTIi9CR nitMxJuXvkNBMtsyv+inmFMegKU6dj1DU93B9JpsFRRvy3TCfj9kRjhKWEpyindo yaZMH3EGOA3ALW5kWyr+XegyYznQbVdDlo/ikO9BAywBOx+DdRG4xYxRdxYt8/HH qXwPAGQe2veMlR7Iq3GjwHLebyuVc+iHbC7feRmNBpAT1RR7J+RIGlDPOBMUpuDT A8HbNzPkoXPGh9vMsREXtR5aPCaZISdcm8DTlNiZCPaX5VHL4SRJ5XjI2rnahaOE rhCFy0mxqQaKnEI9kCWWFmhX/MqzzfiW3yg0qFIAVLDQZZMFJr3jMHIvkxPk09rP nQIjMRBalFXmSiksx8UEhAzyriqiXwwgEI0gJVHcs3EIQGD5jNqvIYTX67/rqSF2 OXoYuq0MHrAJgEfDncXvZFFMuAS/5KMvzSXfWr5/L0ncCU9UykjdPrFvetG/7IXQ BT1TX4pOeW15a6fg6KwSZ5KPrt3o8qtRfW4Ov49hPD2EhnCTMbkCRBbW8F13+9YF xzvC4Vm1r/Oa4TTUbf5tVto7ua/lZvwnu5DIWn2zy5ZUPrtn22r1ymVui7Iuhl0b SRcADdHh3NgrjDjalhLDB95ho5omG39l7qBKBTlBAYJhDuAk9rIk1FCfCB8upztt -----END RSA PRIVATE KEY-----` var keys jwt.KeyRegister n, err := keys.LoadPEM([]byte(pem), []byte("dangerzone")) if err != nil { fmt.Println("load error:", err) } fmt.Println(n, "keys added") }
Output: 1 keys added
func (*KeyRegister) PEM ¶ added in v1.6.0
func (keys *KeyRegister) PEM() ([]byte, error)
PEM exports the (public) keys as PEM-encoded PKIX. Elements from the Secret field, if any, are not included.
type NumericTime ¶
type NumericTime float64
NumericTime implements NumericDate: “A JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds.”
func NewNumericTime ¶
func NewNumericTime(t time.Time) *NumericTime
NewNumericTime returns the the corresponding representation with nil for the zero value. Do t.Round(time.Second) for slightly smaller token production and compatibility. See the bugs section for details.
func (*NumericTime) String ¶
func (n *NumericTime) String() string
String returns the ISO representation or the empty string for nil.
func (*NumericTime) Time ¶
func (n *NumericTime) Time() time.Time
Time returns the Go mapping with the zero value for nil.
type Registered ¶
type Registered struct { // Issuer identifies the principal that issued the JWT. Issuer string `json:"iss,omitempty"` // Subject identifies the principal that is the subject of the JWT. Subject string `json:"sub,omitempty"` // Audiences identifies the recipients that the JWT is intended for. Audiences []string `json:"aud,omitempty"` // Expires identifies the expiration time on or after which the JWT // must not be accepted for processing. Expires *NumericTime `json:"exp,omitempty"` // NotBefore identifies the time before which the JWT must not be // accepted for processing. NotBefore *NumericTime `json:"nbf,omitempty"` // Issued identifies the time at which the JWT was issued. Issued *NumericTime `json:"iat,omitempty"` // ID provides a unique identifier for the JWT. ID string `json:"jti,omitempty"` }
Registered “JSON Web Token Claims” has a subset of the IANA registration. See <https://www.iana.org/assignments/jwt/claims.csv> for the full listing.
Each field is optional—there are no required claims. The string values are case sensitive.
func (*Registered) AcceptAudience ¶ added in v1.5.0
func (r *Registered) AcceptAudience(stringOrURI string) bool
AcceptAudience verifies the applicability of an audience identified as stringOrURI. Any stringOrURI is accepted on absence of the aud(ience) claim.
func (*Registered) AcceptTemporal ¶ added in v1.12.0
AcceptTemporal verifies Issued, NotBefore and Expires each against t when the respective claim is present, i.e., when the NumericTime pointer is not nil.
Notes ¶
Bugs ¶
Some broken JWT implementations fail to parse tokens with fractions in Registered.Expires, .NotBefore or .Issued. Round to seconds—like NewNumericDate(time.Now().Round(time.Second))—for compatibility.