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/appootb/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")) }), Keys: &jwt.KeyRegister{RSAs: []*rsa.PublicKey{&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 var claims jwt.Claims claims.Subject = "lakane" claims.Set = map[string]interface{}{ "fn": "Lana Anthony Kane", } req, _ := http.NewRequest("GET", srv.URL, nil) 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/appootb/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 AlgError
- 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) Valid(t time.Time) bool
- type Handler
- type KeyRegister
- type NumericTime
- type Registered
- type Token
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 = 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.
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.
Functions ¶
This section is empty.
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. Registered // Set has the claims set mapped by name for non-standard usecases. // Use Registered fields where possible. The Sign methods copy each // non-zero Registered field into this map when not nil. JavaScript // numbers are always of the double precision floating-point type. // 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 // “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. The specification uses the term "registered" for standardised claim names, and "private" for non-registered.
Example (ByName) ¶
Typed Claim Lookups
package main import ( "fmt" "time" "github.com/appootb/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 if, and only if, the signature checks out. The return is an AlgError when the algorithm is not in ECDSAAlgs. 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 if, and only if, the signature checks out. The return is an AlgError when the algorithm is not in HMACAlgs. 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 if, and only if, the signature checks out. The return is an AlgError when the algorithm is not in RSAAlgs. 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 updates the Raw field 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.
func (*Claims) ECDSASignHeader ¶
ECDSASignHeader applies ECDSASign on a HTTP request. Specifically it sets a bearer token in the Authorization header.
func (*Claims) HMACSign ¶
HMACSign updates the Raw field and returns a new JWT. The return is an AlgError when alg is not in HMACAlgs.
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 updates the Raw field and returns a new JWT. The return is an AlgError when alg is not in RSAAlgs.
func (*Claims) RSASignHeader ¶
RSASignHeader applies RSASign on a HTTP request. Specifically it sets 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 // 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{} // 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 only passed to Target if the JWT checks out.
Example (Context) ¶
Full Access To The JWT Claims
package main import ( "fmt" "net/http" "net/http/httptest" "github.com/appootb/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, "no deadline") } else { t := jwt.NumericTime(n) fmt.Fprintln(w, "deadline at", t.String()) } }), Keys: &jwt.KeyRegister{Secrets: [][]byte{[]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/appootb/jwt" ) var someECKey *ecdsa.PrivateKey func main() { h := &jwt.Handler{ Target: http.HandlerFunc(func(http.ResponseWriter, *http.Request) { panic("reached target handler") }), Keys: &jwt.KeyRegister{ECDSAs: []*ecdsa.PublicKey{&someECKey.PublicKey}}, 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 without authorization… ") resp := httptest.NewRecorder() h.ServeHTTP(resp, req) fmt.Println("HTTP", resp.Code, resp.Header().Get("WWW-Authenticate")) fmt.Print("Try another 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 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 without authorization… HTTP 401 Bearer Try another algorithm… HTTP 401 Bearer error="invalid_token", error_description="jwt: signature mismatch" Try expired token… HTTP 401 Bearer error="invalid_token", error_description="jwt: time constraints exceeded"
Example (Filter) ¶
Func As A Request Filter
package main import ( "crypto/rsa" "fmt" "net/http" "net/http/httptest" "github.com/appootb/jwt" ) var someRSAKey *rsa.PrivateKey func main() { h := &jwt.Handler{ Target: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Elaborate voicemail hoax!")) }), Keys: &jwt.KeyRegister{RSAs: []*rsa.PublicKey{&someRSAKey.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 }, } // 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 // Optional key identification. // See Claims.KeyID for details. ECDSAIDs []string // ECDSAs key ID mapping RSAIDs []string // RSAs key ID mapping SecretIDs []string // Secrets key ID mapping }
KeyRegister contains 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. 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/appootb/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 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.
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 has the IANA registered “JSON Web Token Claims”. Each one is optional—there are no required claims. String values are case sensitive.
func (*Registered) AcceptAudience ¶ added in v1.5.1
func (r *Registered) AcceptAudience(stringOrURI string) bool
AcceptAudience verifies the applicability of the audience identified with stringOrURI. Any stringOrURI is accepted on absence of the audience claim.
type Token ¶ added in v1.5.1
type Token struct { *Claims // contains filtered or unexported fields }