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"))
}),
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/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 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
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 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.
// Non-standard claims are read 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 is the 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 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/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, "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/pascaldekloe/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/pascaldekloe/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/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 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 are the IANA registered “JSON Web Token Claims”.