Documentation ¶
Overview ¶
Package jwt implements “JSON Web Token (JWT)” RFC 7519. Signatures only; no unsecured nor encrypted tokens.
Example ¶
Claims with the standard HTTP client + server library.
package main import ( "crypto/rsa" "fmt" "io" "net/http" "net/http/httptest" "os" "github.com/pascaldekloe/jwt" ) var someRSAKey *rsa.PrivateKey func main() { // run secured service srv := httptest.NewTLSServer(&jwt.Handler{ Target: http.HandlerFunc(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")) }), RSAKey: &someRSAKey.PublicKey, HeaderBinding: map[string]string{ "sub": "X-Verified-User", // registered [standard] claim name "fn": "X-Verified-Name", // private [custom] claim name }, }) defer srv.Close() // build request with claims req, _ := http.NewRequest("GET", srv.URL, nil) var claims jwt.Claims claims.Subject = "lakane" claims.Set = map[string]interface{}{ "fn": "Lana Anthony Kane", } if err := claims.RSASignHeader(req, jwt.RS512, someRSAKey); err != nil { fmt.Println("sign error:", err) } // call service resp, _ := srv.Client().Do(req) fmt.Println("HTTP", resp.Status) io.Copy(os.Stdout, resp.Body) }
Output: HTTP 200 OK Hello Lana Anthony Kane! You are authorized as lakane.
Example (Extend) ¶
Use custom algorithm.
package main import ( "crypto" _ "crypto/sha1" // must link into binary "fmt" "github.com/pascaldekloe/jwt" ) // HS1 is a SHA1 extension. const HS1 = "HS1" func init() { // static registration jwt.HMACAlgs[HS1] = crypto.SHA1 } // Use custom algorithm. func main() { c := new(jwt.Claims) c.ID = "Me Too!" // issue token, err := c.HMACSign(HS1, []byte("guest")) if err != nil { fmt.Println("sign error:", err) return } fmt.Println("token:", string(token)) // verify got, err := jwt.HMACCheck(token, []byte("guest")) if err != nil { fmt.Println("check error:", err) return } fmt.Println("JSON:", string(got.Raw)) }
Output: token: eyJhbGciOiJIUzEifQ.eyJqdGkiOiJNZSBUb28hIn0.hHye7VnslIM4jO-MoBfggMe8MUQ JSON: {"jti":"Me Too!"}
Index ¶
- Constants
- Variables
- type Claims
- func ECDSACheck(token []byte, key *ecdsa.PublicKey) (*Claims, error)
- func ECDSACheckHeader(r *http.Request, key *ecdsa.PublicKey) (*Claims, error)
- func HMACCheck(token, secret []byte) (*Claims, error)
- func HMACCheckHeader(r *http.Request, secret []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) (token []byte, err error)
- func (c *Claims) ECDSASignHeader(r *http.Request, alg string, key *ecdsa.PrivateKey) error
- func (c *Claims) HMACSign(alg string, secret []byte) (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) (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)
- func (c *Claims) Sync() error
- func (c *Claims) Valid(t time.Time) bool
- type Handler
- type KeyRegister
- type NumericTime
- type Registered
Examples ¶
Constants ¶
const ( ES256 = "ES256" // ECDSA using P-256 and SHA-256 ES384 = "ES384" // ECDSA using P-384 and SHA-384 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 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 is the ECDSA hash algorithm registration. ECDSAAlgs = map[string]crypto.Hash{ ES256: crypto.SHA256, ES384: crypto.SHA384, ES512: crypto.SHA512, } // HMACAlgs is the HMAC hash algorithm registration. HMACAlgs = map[string]crypto.Hash{ HS256: crypto.SHA256, HS384: crypto.SHA384, HS512: crypto.SHA512, } // RSAAlgs is the RSA hash algorithm registration. RSAAlgs = map[string]crypto.Hash{ PS256: crypto.SHA256, PS384: crypto.SHA384, PS512: crypto.SHA512, RS256: crypto.SHA256, RS384: crypto.SHA384, RS512: crypto.SHA512, } )
When adding additional entries you also need to import the respective packages to link the hash function into the binary crypto.Hash.Available.
var ErrAlgUnk = errors.New("jwt: algorithm unknown")
ErrAlgUnk signals an unsupported "alg" value (for the respective method).
var ErrNoHeader = errors.New("jwt: no HTTP Authorization")
ErrNoHeader signals an HTTP request without Authorization.
var ErrSigMiss = errors.New("jwt: signature mismatch")
ErrSigMiss means the signature check failed.
var ErrUnsecured = errors.New("jwt: unsecured—no signature")
ErrUnsecured signals the "none" algorithm.
Functions ¶
This section is empty.
Types ¶
type Claims ¶
type Claims struct { // Registered field values take precedence. Registered // Raw has the JSON payload. This field is read-only. Raw json.RawMessage // Set has the claims set mapped by name for non-standard usecases. // Use Registered fields where possible. Note that JSON/JavaScript // numbers are always of the double precision floating-point type. // Non-standard claims are read as follows. // // 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{} // “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 is JWT payload representation.
Example (ByName) ¶
Typed claim lookups.
package main import ( "fmt" "time" "github.com/pascaldekloe/jwt" ) func main() { offset := time.Unix(1537622794, 0) c := jwt.Claims{ Registered: jwt.Registered{ Issuer: "a", Subject: "b", Audiences: []string{"c"}, Expires: jwt.NewNumericTime(offset.Add(time.Minute)), NotBefore: jwt.NewNumericTime(offset.Add(-time.Second)), Issued: jwt.NewNumericTime(offset), ID: "d", }, } for _, name := range []string{"iss", "sub", "aud", "exp", "nbf", "iat", "jti"} { 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) } } }
Output: "iss": "a" "sub": "b" "aud": "c" "exp": 1537622854 "nbf": 1537622793 "iat": 1537622794 "jti": "d"
func ECDSACheck ¶
ECDSACheck parses a JWT and returns the claims set if, and only if, the signature checks out. Note that this excludes unsecured JWTs ErrUnsecured. When the algorithm is not in ECDSAAlgs, then the error is ErrAlgUnk. See Valid to complete the verification.
func ECDSACheckHeader ¶
ECDSACheckHeader applies ECDSACheck on a HTTP request. Specifically it looks for a bearer token in the Authorization header.
func HMACCheck ¶
HMACCheck parses a JWT and returns the claims set if, and only if, the signature checks out. Note that this excludes unsecured JWTs ErrUnsecured. When the algorithm is not in HMACAlgs, then the error is ErrAlgUnk. See Valid to complete the verification.
func HMACCheckHeader ¶
HMACCheckHeader applies HMACCheck on a HTTP request. Specifically it looks for a bearer token in the Authorization header.
func RSACheck ¶
RSACheck parses a JWT and returns the claims set if, and only if, the signature checks out. Note that this excludes unsecured JWTs ErrUnsecured. When the algorithm is not in RSAAlgs, then the error is ErrAlgUnk. See Valid to complete the verification.
func RSACheckHeader ¶
RSACheckHeader applies RSACheck on a HTTP request. Specifically it looks for a bearer token in the Authorization header.
func (*Claims) ECDSASign ¶
ECDSASign calls Sync and returns a new JWT. When the algorithm is not in ECDSAAlgs, then the error is ErrAlgUnk. 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.
func (*Claims) ECDSASignHeader ¶
ECDSASignHeader applies ECDSASign on a HTTP request. Specifically it sets a bearer token in the Authorization header.
func (*Claims) HMACSign ¶
HMACSign calls Sync and returns a new JWT. When the algorithm is not in HMACAlgs, then the error is ErrAlgUnk.
func (*Claims) HMACSignHeader ¶
HMACSignHeader applies HMACSign on a 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.
func (*Claims) RSASign ¶
RSASign calls Sync and returns a new JWT. When the algorithm is not in RSAAlgs, then the error is ErrAlgUnk.
func (*Claims) RSASignHeader ¶
RSASignHeader applies RSASign on a HTTP request. Specifically it sets a bearer token in the Authorization header.
func (*Claims) String ¶
String returns the claim when present and if the representation is a JSON string.
type Handler ¶
type Handler struct { // Target is the secured service. Target http.Handler // Secret is the HMAC key. Secret []byte // ECDSAKey applies ECDSAAlgs and disables HMACAlgs when set. ECDSAKey *ecdsa.PublicKey // RSAKey applies RSAAlgs and disables HMACAlgs when set. RSAKey *rsa.PublicKey // Keys disables Secret, ECDSAKey and RSAKey when set. 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 // 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 string // 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) }
Handler protects an http.Handler with security enforcements. Requests are passed to Target only when the JWT checks out.
Example (Context) ¶
Full access to the JWT claims.
package main import ( "fmt" "net/http" "net/http/httptest" "github.com/pascaldekloe/jwt" ) func main() { h := &jwt.Handler{ Target: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { claims := req.Context().Value("verified-jwt").(*jwt.Claims) if n, ok := claims.Number("deadline"); !ok { fmt.Fprintln(w, "you don't have a deadline") } else { t := jwt.NumericTime(n) fmt.Fprintln(w, "deadline at", t.String()) } }), Secret: []byte("killarcherdie"), ContextKey: "verified-jwt", } req := httptest.NewRequest("GET", "/status", nil) req.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiJ9.eyJkZWFkbGluZSI6NjcxNTAwNzk5fQ.yeUUNOj4-RvNp5Lt0d3lpS7MTgsS_Uk9XnsXJ3kVLhw") 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 (Deny) ¶
Standard compliant security out-of-the-box.
package main import ( "crypto/ecdsa" "fmt" "net/http" "net/http/httptest" "time" "github.com/pascaldekloe/jwt" ) var someECKey *ecdsa.PrivateKey func main() { h := &jwt.Handler{ ECDSAKey: &someECKey.PublicKey, Target: http.HandlerFunc(func(http.ResponseWriter, *http.Request) { panic("reached target handler") }), Func: func(w http.ResponseWriter, req *http.Request, claims *jwt.Claims) (pass bool) { panic("reached JWT-enhanced handler") }, } req := httptest.NewRequest("GET", "/had-something-for-this", nil) fmt.Print("Try with no authorization… ") resp := httptest.NewRecorder() h.ServeHTTP(resp, req) fmt.Println("HTTP", resp.Code, resp.Header().Get("WWW-Authenticate")) fmt.Print("Try with disabled algorithm… ") var c jwt.Claims if err := c.HMACSignHeader(req, jwt.HS512, []byte("guest")); err != nil { fmt.Println("sign error:", err) } resp = httptest.NewRecorder() h.ServeHTTP(resp, req) fmt.Println("HTTP", resp.Code, resp.Header().Get("WWW-Authenticate")) fmt.Print("Try with expired token… ") c.Expires = jwt.NewNumericTime(time.Now().Add(-time.Second)) if err := c.ECDSASignHeader(req, jwt.ES512, someECKey); err != nil { fmt.Println("sign error:", err) } resp = httptest.NewRecorder() h.ServeHTTP(resp, req) fmt.Println("HTTP", resp.Code, resp.Header().Get("WWW-Authenticate")) }
Output: Try with no authorization… HTTP 401 Bearer Try with disabled algorithm… HTTP 401 Bearer error="invalid_token", error_description="jwt: algorithm unknown" Try with expired token… HTTP 401 Bearer error="invalid_token", error_description="jwt: time constraints exceeded"
Example (Filter) ¶
Use Func as a request filter.
package main import ( "crypto/rsa" "fmt" "net/http" "net/http/httptest" "github.com/pascaldekloe/jwt" ) var someRSAKey *rsa.PrivateKey func main() { h := &jwt.Handler{ Keys: &jwt.KeyRegister{ RSAs: []*rsa.PublicKey{&someRSAKey.PublicKey}, }, Target: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Elaborate voicemail hoax!")) }), 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 }, } // build request req := httptest.NewRequest("GET", "/urgent", nil) if err := new(jwt.Claims).RSASignHeader(req, jwt.PS512, someRSAKey); err != nil { fmt.Println("sign error:", err) } // get response resp := httptest.NewRecorder() h.ServeHTTP(resp, req) fmt.Println("HTTP", resp.Code, resp.Body) }
Output: HTTP 503 Ring, ring!
type KeyRegister ¶
type KeyRegister struct { ECDSAs []*ecdsa.PublicKey // ECDSA credentials RSAs []*rsa.PublicKey // RSA credentials Secrets [][]byte // HMAC credentials }
KeyRegister contains recognized credentials.
func (*KeyRegister) Check ¶
func (keys *KeyRegister) Check(token []byte) (*Claims, error)
Check parses a JWT and returns the claims set if, and only if, the signature checks out. Note that this excludes unsecured JWTs ErrUnsecured. See Claims.Valid to complete the verification.
func (*KeyRegister) CheckHeader ¶
func (keys *KeyRegister) CheckHeader(r *http.Request) (*Claims, error)
CheckHeader applies KeyRegister.Check on a HTTP request. Specifically it looks for a bearer token in the Authorization header.
func (*KeyRegister) LoadPEM ¶
func (keys *KeyRegister) LoadPEM(data, password []byte) (n int, err error)
LoadPEM adds keys from PEM-encoded data and returns the count. PEM encryption is enforced for non-empty password values. The source may be certificates, public keys, private keys, or a combination of any of the previous. Private keys are discared after the (automatic) public key extraction completes.
Example (Encrypted) ¶
PEM with password protection.
package main import ( "fmt" "github.com/pascaldekloe/jwt" ) func main() { const pem = `-----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("got", n, "keys") }
Output: got 1 keys
type NumericTime ¶
type NumericTime float64
NumericTime, named NumericDate, is “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.
func (*NumericTime) String ¶
func (n *NumericTime) String() string
String returns the ISO representation with 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 are the IANA registered “JSON Web Token Claims”.