jwt: github.com/pascaldekloe/jwt Index | Examples | Files | Directories

package jwt

import "github.com/pascaldekloe/jwt"

Package jwt implements “JSON Web Token (JWT)” RFC 7519. Signatures only; no unsecured nor encrypted tokens.

Claims With The Standard HTTP Library

Code:

publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
    fmt.Println("key generation error:", err)
    return
}

// 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{publicKey}},

    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()

// self-signed request
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.EdDSASignHeader(req, privateKey); 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.

Use custom algorithm.

Code:

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))

}

Index

Examples

Package Files

check.go jwt.go register.go sign.go web.go

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
    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.

type AlgError Uses

type AlgError string

AlgError signals that the specified algorithm is not in use.

func (AlgError) Error Uses

func (e AlgError) Error() string

Error honors the error interface.

type Claims Uses

type Claims struct {
    // Registered field values take precedence.
    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

    // “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.

Typed Claim Lookups

Code:

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 Uses

func ECDSACheck(token []byte, key *ecdsa.PublicKey) (*Claims, error)

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 Uses

func ECDSACheckHeader(r *http.Request, key *ecdsa.PublicKey) (*Claims, error)

ECDSACheckHeader applies ECDSACheck on a HTTP request. Specifically it looks for a bearer token in the Authorization header.

func EdDSACheck Uses

func EdDSACheck(token []byte, key ed25519.PublicKey) (*Claims, error)

EdDSACheck parses a JWT if, and only if, the signature checks out. See Valid to complete the verification.

func EdDSACheckHeader Uses

func EdDSACheckHeader(r *http.Request, key ed25519.PublicKey) (*Claims, error)

EdDSACheckHeader applies EdDSACheck on a HTTP request. Specifically it looks for a bearer token in the Authorization header.

func HMACCheck Uses

func HMACCheck(token, secret []byte) (*Claims, error)

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 Uses

func HMACCheckHeader(r *http.Request, secret []byte) (*Claims, error)

HMACCheckHeader applies HMACCheck on a HTTP request. Specifically it looks for a bearer token in the Authorization header.

func RSACheck Uses

func RSACheck(token []byte, key *rsa.PublicKey) (*Claims, error)

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 Uses

func RSACheckHeader(r *http.Request, key *rsa.PublicKey) (*Claims, error)

RSACheckHeader applies RSACheck on a HTTP request. Specifically it looks for a bearer token in the Authorization header.

func (*Claims) ECDSASign Uses

func (c *Claims) ECDSASign(alg string, key *ecdsa.PrivateKey) (token []byte, err error)

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 Uses

func (c *Claims) ECDSASignHeader(r *http.Request, alg string, key *ecdsa.PrivateKey) error

ECDSASignHeader applies ECDSASign on a HTTP request. Specifically it sets a bearer token in the Authorization header.

func (*Claims) EdDSASign Uses

func (c *Claims) EdDSASign(key ed25519.PrivateKey) (token []byte, err error)

EdDSASign updates the Raw field and returns a new JWT.

func (*Claims) EdDSASignHeader Uses

func (c *Claims) EdDSASignHeader(r *http.Request, key ed25519.PrivateKey) error

EdDSASignHeader applies ECDSASign on a HTTP request. Specifically it sets a bearer token in the Authorization header.

func (*Claims) HMACSign Uses

func (c *Claims) HMACSign(alg string, secret []byte) (token []byte, err error)

HMACSign updates the Raw field and returns a new JWT. The return is an AlgError when alg is not in HMACAlgs.

func (*Claims) HMACSignHeader Uses

func (c *Claims) HMACSignHeader(r *http.Request, alg string, secret []byte) error

HMACSignHeader applies HMACSign on a HTTP request. Specifically it sets a bearer token in the Authorization header.

func (*Claims) Number Uses

func (c *Claims) Number(name string) (value float64, ok bool)

Number returns the claim when present and if the representation is a JSON number.

func (*Claims) RSASign Uses

func (c *Claims) RSASign(alg string, key *rsa.PrivateKey) (token []byte, err error)

RSASign updates the Raw field and returns a new JWT. The return is an AlgError when alg is not in RSAAlgs.

func (*Claims) RSASignHeader Uses

func (c *Claims) RSASignHeader(r *http.Request, alg string, key *rsa.PrivateKey) error

RSASignHeader applies RSASign on a HTTP request. Specifically it sets a bearer token in the Authorization header.

func (*Claims) String Uses

func (c *Claims) String(name string) (value string, ok bool)

String returns the claim when present and if the representation is a JSON string.

func (*Claims) Valid Uses

func (c *Claims) Valid(t time.Time) bool

Valid returns whether the claims set may be accepted for processing at the given moment in time. If time is zero, then Valid returns whether there are any time constraints at all.

type Handler Uses

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. HeaderBinding entries that don't match the prefix
    // are ignored.
    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{}

    // 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.

Full Access To The JWT Claims

Code:

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

Standard Compliant Security Out-of-the-box

Code:

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"

Func As A Request Filter

Code:

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!

func (*Handler) ServeHTTP Uses

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP honors the http.Handler interface.

type KeyRegister Uses

type KeyRegister struct {
    ECDSAs  []*ecdsa.PublicKey  // ECDSA credentials
    EdDSAs  []ed25519.PublicKey // EdDSA credentials
    RSAs    []*rsa.PublicKey    // RSA credentials
    Secrets [][]byte            // HMAC credentials

    // Optional key identification. See Claims.KeyID for details.
    // Non-empty values match the respective keys (or secrets).
    ECDSAIDs  []string // ECDSAs key ID mapping
    EdDSAIDs  []string // EdDSA key ID mapping
    RSAIDs    []string // RSAs key ID mapping
    SecretIDs []string // Secrets key ID mapping
}

KeyRegister contains recognized credentials.

func (*KeyRegister) Check Uses

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 Uses

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 Uses

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.

PEM With Password Protection

Code:

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 Uses

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 Uses

func NewNumericTime(t time.Time) *NumericTime

NewNumericTime returns the the corresponding representation with nil for the zero value.

func (*NumericTime) String Uses

func (n *NumericTime) String() string

String returns the ISO representation or the empty string for nil.

func (*NumericTime) Time Uses

func (n *NumericTime) Time() time.Time

Time returns the Go mapping with the zero value for nil.

type Registered Uses

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” is 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 Uses

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.

Directories

PathSynopsis
xPackage x provides experimental functionality.

Package jwt imports 21 packages (graph) and is imported by 6 packages. Updated 2019-08-09. Refresh now. Tools for package owners.

The go get command cannot install this package because of the following issues: