jwt

package module
v0.1.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2024 License: MIT Imports: 20 Imported by: 1

README

GoDoc

Yet another jwt lib

This is a simple lib made for small footprint and easy usage

It allows creating, signing, reading and verifying jwt tokens easily (see code examples below).

JWT?

JWT.io has a great introduction to JSON Web Tokens.

In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for Bearer tokens in Oauth 2. A token is made of three parts, separated by .'s. The first two parts are JSON objects, that have been base64url encoded. The last part is the signature, encoded the same way.

The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used.

The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to RFC 7519 for information about reserved keys and the proper way to add your own.

(courtesy of golang-jwt).

Why another jwt lib?

The main issue I have with the existing JWT lib is that the syntax is too heavy and I had something else in mind in terms of what would make a convenient JWT lib. I've had also issues with it performing checks on incoming crypto.Signer objects that prevent third party signature providers such has hardware modules, and a few other things. JWT is a simple enough standard so building a new lib isn't that much work.

Note that all algos are always linked (hmac, rsa, ecdsa, ed25519). All libs are also pulled by go's crypto/x509 so you probably have these already compiled in. If go decides to avoid building these in, then I will move these in submodules, but for now there is no need to do so.

TODO

There are some things that still remain to be done:

  • Implement more verification methods
  • Test, test and test
  • Write more documentation
  • Support encrypted JWT tokens
  • Apply Payload to go objects using reflect

Examples

Create & sign a new token

import _ "crypto/sha256"

priv := []byte("this is a hmac key")
tok := jwt.New(jwt.HS256)
tok.Header().Set("kid", keyId) // syntax to set header values
tok.Payload().Set("iss", "myself")
tok.Payload().Set("exp", time.Now().Add(365*24*time.Hour).Unix())
signedToken, err := tok.Sign(rand.Reader, priv)

Verify a token

import _ "crypto/sha256"

token, err := jwt.ParseString(input)
if err != nil {
	...
}
publicKey := fetchPublicKey(token.GetKeyId())
err = token.Verify(jwt.VerifyAlgo(jwt.ES256, jwt.RS256), jwt.VerifySignature(publicKey), jwt.VerifyExpiresAt(time.Now(), false))
if err != nil {
	...
}
log.Printf("token iss value = %s", token.Payload().Get("iss"))

Create a non-json token

import _ "crypto/sha256"

priv := []byte("this is a hmac key")
tok := jwt.New(jwt.HS256)
tok.Header().Set("kid", keyId)
tok.SetRawPayload(binData, "octet-stream") // can pass cty="" to not set content type
signedToken, err := tok.Sign(priv)

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidToken           = errors.New("jwt: invalid token provided")
	ErrNoSignature            = errors.New("jwt: token has no signature")
	ErrInvalidSignature       = errors.New("jwt: token signature is not valid")
	ErrInvalidSignKey         = errors.New("jwt: invalid key provided for signature")
	ErrInvalidSignatureLength = errors.New("jwt: token signature is not valid (bad length)")
	ErrHashNotAvailable       = errors.New("jwt: hash method not available")
	ErrNoHeader               = errors.New("jwt: header is not available (parsing failed?)")
	ErrNoPayload              = errors.New("jwt: payload is not available (parsing failed?)")
	ErrInvalidPublicKey       = errors.New("jwt: invalid public key provided")
	ErrNoPrivateKey           = errors.New("jwt: private key is missing")

	ErrVerifyMissing = errors.New("jwt: a claim required for verification is missing")
	ErrVerifyFailed  = errors.New("jwt: claim verification has failed")
)
View Source
var (
	DeprecatedAllowEcdsaASN1Signatures = true // this will turn to false eventually
)

Functions

func RegisterAlgo

func RegisterAlgo(obj Algo)

RegisterAlgo allows registration of custom algorithms. We assume this will be called during init in a single thread, so no locking is performed.

Types

type Algo

type Algo interface {
	// String should return the name of the algo, for example "HS256"
	String() string

	// Sign should sign the provided buffer, and return the resulting
	// signature. If the private key isn't of the appropriate type, an
	// error should be triggered.
	Sign(rand io.Reader, buf []byte, priv crypto.PrivateKey) ([]byte, error)

	// Verify must verify the provided signature and return an error
	// if the public key is not of the appropriate type or the signature
	// is not valid.
	Verify(buf, sign []byte, pub crypto.PublicKey) error
}

Algo is a jwt signature algorithm. Typical values include HS256 and ES256. By implementing this interface, you can also add support for your own custom types. Remember to call RegisterAlgo() so your new algo can be recognized appropriately.

var (
	HS256 Algo = hmacAlgo(crypto.SHA256).reg()
	HS384 Algo = hmacAlgo(crypto.SHA384).reg()
	HS512 Algo = hmacAlgo(crypto.SHA512).reg()

	RS256 Algo = rsaAlgo(crypto.SHA256).reg()
	RS384 Algo = rsaAlgo(crypto.SHA384).reg()
	RS512 Algo = rsaAlgo(crypto.SHA512).reg()

	PS256 Algo = rsaPssAlgo(crypto.SHA256).reg()
	PS384 Algo = rsaPssAlgo(crypto.SHA384).reg()
	PS512 Algo = rsaPssAlgo(crypto.SHA512).reg()

	ES256 Algo = ecdsaAlgo(crypto.SHA256).reg()
	ES384 Algo = ecdsaAlgo(crypto.SHA384).reg()
	ES512 Algo = ecdsaAlgo(crypto.SHA512).reg()

	EdDSA Algo = ed25519Algo{}.reg()
	None  Algo = noneAlgo{}.reg()
)

note: the .reg() just performs a call to RegisterAlgo() and returns the object itself.

type Header map[string]string

Header type holds values from the token's header for easy access

func (Header) Get

func (h Header) Get(key string) string

Get will return the value of the requested key from the header, or an empty string if the value is not found.

func (Header) GetAlgo

func (h Header) GetAlgo() Algo

GetAlgo will return a Algo based on the alg value of the header, or nil if the algo is invalid or unknown. This will also work with custom algo as long as RegisterAlgo() was called.

func (Header) Has

func (h Header) Has(key string) bool

Has returns true if the key exists in the header (and there is a header), and can be used to test for a given key even if its value is empty.

func (Header) Set

func (h Header) Set(key, value string) error

Set will update the key's value in the header and return nil. If there is no header (because it failed to parse, for example), Set will return an ErrNoHeader error. Calling Set on a nil Header will not panic.

type JWK added in v0.1.1

type JWK struct {
	PrivateKey crypto.PrivateKey `json:"-"`
	PublicKey  crypto.PublicKey  `json:"-"`
	KeyID      string            `json:"kid,omitempty"`
	Algo       string            `json:"alg,omitempty"` // RSA-OAEP-256
	Use        string            `json:"use,omitempty"`
	Ext        bool              `json:"ext,omitempty"`
	KeyOps     []string          `json:"key_ops,omitempty"`
}

func (*JWK) ApplyValues added in v0.1.1

func (jwk *JWK) ApplyValues(values map[string]any) error

func (*JWK) ExportRequiredPublicValues added in v0.1.1

func (jwk *JWK) ExportRequiredPublicValues() map[string]any

func (*JWK) ExportRequiredValues added in v0.1.1

func (jwk *JWK) ExportRequiredValues() map[string]any

func (*JWK) ExportValues added in v0.1.1

func (jwk *JWK) ExportValues() map[string]any

func (*JWK) MarshalJSON added in v0.1.1

func (jwk *JWK) MarshalJSON() ([]byte, error)

func (*JWK) Public added in v0.1.1

func (jwk *JWK) Public() crypto.PublicKey

func (*JWK) Sign added in v0.1.6

func (jwk *JWK) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)

func (*JWK) String added in v0.1.1

func (jwk *JWK) String() string

func (*JWK) Thumbprint added in v0.1.1

func (jwk *JWK) Thumbprint(method crypto.Hash) ([]byte, error)

func (*JWK) ThumbprintHex added in v0.1.1

func (jwk *JWK) ThumbprintHex(method crypto.Hash) string

func (*JWK) UnmarshalJSON added in v0.1.1

func (jwk *JWK) UnmarshalJSON(v []byte) error

type Payload

type Payload map[string]any

func (Payload) Get

func (b Payload) Get(key string) any

Get is a safe get that will return nil if the body itself is null or the value is nil. If you want to check if a value exists or not, use Has().

func (Payload) GetFloat

func (b Payload) GetFloat(key string) float64

GetFloat will attempt to parse the requested key as a float and return it. If the value is an int or any other kind of number-y value, it will be converted to float64 and returned, or return 0 in case of failure.

func (Payload) GetInt

func (b Payload) GetInt(key string) int64

GetInt will attempt to parse the requested key as an integer and return it. If the value is a float or any other kind of number-y value, it will be converted (truncated) and returned as an int, or 0 in case of failure.

func (Payload) GetNumericDate

func (b Payload) GetNumericDate(key string) time.Time

GetNumericDate will return a time value based on the requested header, or a zero time if the parsing failed or the key is not set. Check IsZero() for success.

func (Payload) GetString

func (b Payload) GetString(key string) string

GetString will get a value as a string, convert it to a string if possible or return an empty string if the value is not set or cannot be converted. GetString will return an empty string in case of failure.

func (Payload) Has

func (b Payload) Has(key string) bool

Has returns true if the payload was parsed and the key exists.

func (Payload) Set

func (b Payload) Set(key string, value any) error

Set will set the specified value in the payload. It will return an error if the payload failed to parse, for example because it is not a JSON object.

type Token

type Token struct {
	// contains filtered or unexported fields
}

Token represents a JWT token

func New

func New(alg Algo) *Token

New will return a fresh and empty token that can be filled with information to be later signed using the Sign method. By default only the "alg" value of the header will be set.

func ParseString

func ParseString(value string) (*Token, error)

ParseString will generate a Token object from an encoded string. No verification is performed at this point, so it is up to you to call the Verify method.

func (*Token) GetAlgo

func (tok *Token) GetAlgo() Algo

GetAlgo will determine the algorithm in use from the header and return the appropriate Algo object, or nil if unknown or no algo is specified.

func (*Token) GetContentType added in v0.0.3

func (tok *Token) GetContentType() string

GetContentType returns the value of "cty" claim in the token's header, ro "application/jwt" if not set. It will prepend "application/" to values that have no slashes in them as defined in RFC 7515, Section 4.1.10.

func (*Token) GetKeyId

func (tok *Token) GetKeyId() string

GetKeyId is a short hand for Header().Get("kid").

func (*Token) GetRawPayload added in v0.0.3

func (tok *Token) GetRawPayload() ([]byte, error)

GetRawPayload returns the raw value for the token's payload, or an error if it could not be decoded.

func (*Token) GetRawSignature added in v0.0.3

func (tok *Token) GetRawSignature() ([]byte, error)

GetRawSignature returns the raw signature of a parsed token or of a freshly signed token.

func (Token) GetSignString added in v0.0.3

func (tok Token) GetSignString() []byte

GetSignString is used by VerifySignature to get the part of the string that is used to generate a signature. It avoids duplicating memory in order to provide better performance.

func (*Token) Header

func (tok *Token) Header() Header

Header returns the decoded header part of the token and is useful to read the key id value for the signature.

func (*Token) Payload

func (tok *Token) Payload() Payload

Payload returns the payload part of the token, which contains the claims. If parsing failed, then this function will return nil. Payload methods such as Get() and Set() can still be called without causing a panic.

func (*Token) SetRawPayload added in v0.0.3

func (tok *Token) SetRawPayload(payload []byte, cty string) error

SetRawPayload sets the raw value of payload to any kind of data that can be later signed. This can be used to store non-JSON data in the payload.

func (*Token) Sign

func (tok *Token) Sign(rand io.Reader, priv crypto.PrivateKey) (string, error)

Sign will generate the token and sign it, making it ready for distribution.

func (*Token) Verify

func (tok *Token) Verify(opts ...VerifyOption) error

Verify will perform the verifications passed as parameter in sequence, stopping at the first failure. If all verifications are successful, nil will be returned.

type VerifyOption

type VerifyOption func(*Token) error

func VerifyAlgo

func VerifyAlgo(algo ...Algo) VerifyOption

VerifyAlgo returns a VerifyOption that will ensure the token's alg value is one of the specified algos. This allows to easily limit the acceptable signature scheme and should always be used.

func VerifyExpiresAt

func VerifyExpiresAt(now time.Time, req bool) VerifyOption

VerifyExpiresAt returns a VerifyOption that will check the token's expiration to not be before now.

Example use: VerifyExpiresAt(time.Now(), false)

func VerifyMultiple

func VerifyMultiple(opts ...VerifyOption) VerifyOption

VerifyMultiple compounds multiple conditions and fails if any of the passed condition fails. This will return success if no options are passed at all.

func VerifyNotBefore

func VerifyNotBefore(now time.Time, req bool) VerifyOption

VerifyNotBefore returns a VerifyOption that will check the token's not before claim (nbf).

Example use: VerifyNotBefore(time.Now(), false)

func VerifySignature

func VerifySignature(pub crypto.PublicKey) VerifyOption

VerifySignature will check the token's signature against the specified public key based on the algo used for the token. This will always fail for tokens which alg is set to "none".

func VerifyTime

func VerifyTime(now time.Time, req bool) VerifyOption

VerifyTime will verify both the not before and the expires at claims, and is typically used with req=false so those checks only happen if the claims are specified.

If you know both nbf and exp claims will always be there, setting req=true will ensure this and improve security.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL