README

JWIT

JWIT is a tiny Go library built around go-jose that brings JSON Web Tokens (JWTs) and JSON Web Key Sets (JWKS) into your apps.

JWIT features:

  • A high-level API to sign and verify your asymmetric JWTs.
  • A high-level API to publish your public JWKS.

💡 As JWIT sticks to the standards and is not tight to any framework, you can actually pick which features you want to use. You can use it to just sign JWTs, just verify JWTs you get from a third-party and your own servers, or just expose your public JWKS.

One neat use-case:

  1. Your authorization server uses JWIT to sign new JWTs.
  2. Your authorization server uses JWIT to expose its public keys as a JWKS (usually at /.well-known/jwks.json).
  3. Your resource server uses JWIT to unmarshal incoming JWTs and validate them against your authorization server's JWKS.

🤯 JWIT will automatically catch changes to the JWKS. Rotating your secrets has never been so easy.

Installation

go get github.com/gilbsgilbs/jwit

Overview

This section shows a few basic examples that'll give you a sneak peak of how simple it is to work with JWIT. For more in-depth examples (such as working with private claims, loading keys from PEM, …), please head to the godoc page.

Create a signed JWT
// 1. Create a signer from a JSON Web Key Set (JWKS). The JWKS payload will typically reside your
//    authorization server's config or in a secure vault.
signer, err := jwit.NewSigner([]byte(`{"keys": [ ... ]}`))

// 2. Create a JWT that expires in one hour.
rawJWT, err := signer.SignJWT(jwit.C{Duration: 1 * time.Hour})

// 3. That's it, simple as that. rawJWT is a signed JWT token that is ready to serve.
fmt.Println(rawJWT)
Verify a JWT
// 1. Create a verifier
verifier, err := jwit.NewVerifier(
    // Recommended: specify an URL to the issuer's public JWKS.
    //              this will allow JWIT to catch changes to the JWKS.
    &jwit.Issuer{
        // This should correspond to the "iss" claims of the JWTs
        Name: "myVeryOwnIssuer",

        // This is an HTTP(S) URL where the authorization server publishes its public keys.
        // It will be queried the first time a JWT is verified and then periodically.
        JWKSURL: "https://my-very-own-issuer.com/.well-known/jwks.json",

        // You can specify how long the issuer's public keys should be kept in cache.
        // Passed that delay, the JWKS will be re-fetched once asynchronously.
        // Defaults to 24 hours.
        TTL: 10 * time.Hour,
    },
    // Alternatively: pass in public keys directly.
    &jwit.Issuer{
        Name: "myOtherIssuer",
        PublicKeys: []interface{}{
            // using Go's crypto types
            rsaPublicKey,
            ecdsaPublicKey,
            // or using a marshalled JWKS JSON
            []byte(`{"keys": [ … your JWKS … ]}`),
            // or using marshalled PEM blocks
            []byte(`-----BEGIN RSA PUBLIC KEY----- ... -----END RSA PUBLIC KEY-----`),
        },
    },
    // ... you can specify as many issuer as you want
)

// 2. Verify the JWT using its "iss" claim.
isValid, err := verifier.VerifyJWT(rawJWT)
Expose the public JWKS
http.HandleFunc(
    "/.well-known/jwks.json",
    func (w http.ResponseWriter, req *http.Request) {
        // Just get the public JWKS from the signer.
        jwks, err := signer.DumpPublicJWKS()
        if err != nil {
            panic(err)
        }

        // And write it to the response body
        w.Write(jwks)
    },
)

🔒 Security

If you found a security vulnerability in JWIT itslef, do not reveal it publicly and adopt a responsible disclosure. You may open a GitHub issue stating that you found a vulnerability and specifying a safe way to get in touch with you.

Note that JWIT is not another JWT/JWKS implementation by any mean. JWIT relies on go-jose, a popular JWx implementation by Square. On top of that, go-jose and Go's stdlib are the only dependencies to this library. This greatly reduces the attack surface of JWIT. If you found a security vulnerability in go-jose , plese refer to their bug bounty program.

Documentation

Overview

    JWIT makes it easy to work with JWKS and asymmetric JWTs in your apps.

    Checkout https://github.com/gilbsgilbs/jwit for a quick overview.

    Example (ExposeJWKS)

      This example shows how you can expose your public JWKS to the world.

      Output:
      
      
      Example (SignJWT)

        This is a simple example of how to sign a JWT from a JWKS containing private keys.

        Output:
        
        
        Example (SignJWTWithPrivateClaims)

          This example shows how to sign a JWT with private claims .

          Output:
          
          
          Example (SignerFromGoCrypto)

            This example shows how to create a new signer using private keys from go's crypto package.

            Output:
            
            
            Example (SignerFromPEM)

              This shows how you can create a new signer using a PEM file as signing keys. Note however that it is recommended you used JWKS instead.

              Output:
              
              
              Example (SignerGracefullyRenewSigningKeys)

                This example explains step-by-step how to gracefully renew signing keys.

                Output:
                
                
                Example (VerifyJWT)
                Output:
                
                
                Example (VerifyJWTUnmarshalPrivateClaims)

                  This example shows how to unmarshal private claims from a JWT.

                  Output:
                  
                  
                  Example (VerifyJWTWithGoCryptoKeys)

                    Verifying a JWT against a specific set of keys can sometimes be useful (for example if the JWT issuer doesn't provide an "iss" claim or if you don't know the issuer's public key in advance). This example demonstrates how you can validate a JWT using your own set of public keys on an existing verifier.

                    Output:
                    
                    

                    Index

                    Examples

                    Constants

                    This section is empty.

                    Variables

                    View Source
                    var (
                    	// ErrUnknownIssuer indicates that token is not issued by a known issuer.
                    	ErrUnknownIssuer = errors.New("jwit: the JWT issuer is unknown")
                    
                    	// ErrUnexpectedSignature indicates that none of the issuer's JWK was used to sign the token.
                    	ErrUnexpectedSignature = errors.New("jwit: the JWT wasn't signed with a known signature")
                    
                    	// ErrJWKSFetchFailed indicates that we got a non-2xx HTTP status whil fetching the JWKS.
                    	ErrJWKSFetchFailed = errors.New("jwit: couldn't fetch JWKS from server")
                    )

                      Verifier errors

                      View Source
                      var (
                      	// ErrUnknownKeyType indicates that the provided public/private key type is unknown.
                      	ErrUnknownKeyType = errors.New("jwit: provided public/private key type is unknown")
                      )

                        Signer errors

                        Functions

                        This section is empty.

                        Types

                        type C

                        type C = RegisteredClaims

                          C is a shortcut for RegisteredClaims.

                          type Issuer

                          type Issuer struct {
                          	sync.Mutex
                          
                          	// Name is the name of the issuer (corresponding to a "iss" claim)
                          	Name string
                          
                          	// PublicKeys is a set of known public keys for this issuer.
                          	PublicKeys []interface{}
                          
                          	// JWKSURL is an URL where the issuer publishes its JWKS.
                          	JWKSURL string
                          
                          	// TTL defines how long a JWKS are considered "fresh". Past that TTL, jwit will try
                          	// to refresh the JWKS asynchronously.
                          	TTL time.Duration
                          	// contains filtered or unexported fields
                          }

                            Issuer is a third-party that publishes a set of public keys.

                            type RegisteredClaims

                            type RegisteredClaims struct {
                            	// Duration is provided as an alternative to the Expiration Time ("exp") claim.
                            	Duration time.Duration
                            
                            	Issuer    string    // iss
                            	Subject   string    // sub
                            	Audience  []string  // aud
                            	Expiry    time.Time // exp
                            	NotBefore time.Time // nbf
                            	IssuedAt  time.Time // iat
                            	ID        string    // jit
                            }

                              RegisteredClaims as per RFC7919. See https://tools.ietf.org/html/rfc7519#section-4.1

                              type Signer

                              type Signer struct {
                              	DefaultClaims RegisteredClaims
                              	// contains filtered or unexported fields
                              }

                                Signer will help you sign your JWTs and expose your public JWKS.

                                func NewSigner

                                func NewSigner(signingKeysBytes []byte, otherKeysBytes ...[]byte) (*Signer, error)

                                  NewSigner creates a new signer from marshalled JWKS or PEM payloads. signingBytes are the JWKS or PEM private keys that will be used to sign the JWTs. otherBytes can contains public or private JWKs or PEMs that will be used in addition to signing keys to expose your public JWKS.

                                  func NewSignerFromCryptoKeys

                                  func NewSignerFromCryptoKeys(signingKeys []crypto.PrivateKey, otherKeys ...interface{}) (*Signer, error)

                                    NewSignerFromCrytoKeys creates a new Signer from go crypto keys. Signing keys are private keys that will be picked at random to sign new JWTs. Other keys can be public or private keys and will be used in addition to signing keys to expose your public JWKS.

                                    func NewSignerFromJWKS

                                    func NewSignerFromJWKS(signingJWKSBytes []byte, otherJWKSBytes ...[]byte) (*Signer, error)

                                      NewSignerFromJWKS creates a new signer from marshalled JWKS payloads. Signing JWKS are the JWKS that will be used to sign the JWTs. Other JWKS can contains public or private JWKs that will be used in addition to signing keys to expose your public JWKS.

                                      func NewSignerFromPEM

                                      func NewSignerFromPEM(signingPEMBytes []byte, otherPEMBytes ...[]byte) (*Signer, error)

                                        NewSignerFromPEM creates a new signer from PEM-encoded data. Signing PEMs are the private keys that will be used to sign the JWTs. Other PEMs can contain public or private keys that will be used in addition to signing keys to expose your public JWKS.

                                        func (*Signer) DumpOtherJWKS

                                        func (signer *Signer) DumpOtherJWKS() ([]byte, error)

                                          DumpOtherJWKS returns the other JWKS for this signer.

                                          /!\ Do not make this key public /!\

                                          func (*Signer) DumpPublicJWKS

                                          func (signer *Signer) DumpPublicJWKS() ([]byte, error)

                                            DumpPublicJWKS returns the JWKS corresponding to the public keys of this signer. The return value of this function is safe to expose publicly (usually at /.well-known/jwks.json)

                                            func (*Signer) DumpSigningJWKS

                                            func (signer *Signer) DumpSigningJWKS() ([]byte, error)

                                              DumpSigningJWKS returns the signing JWKS for this signer.

                                              /!\ Do not make this key public /!\

                                              func (*Signer) SignJWT

                                              func (signer *Signer) SignJWT(registeredClaims C, privateClaims ...interface{}) (string, error)

                                                SignJWT returns a signed JWT with one of signer's signing keys picked at random.

                                                type Verifier

                                                type Verifier struct {
                                                	// Issuers a set of trusted issuers, mapped by name (corresponding to the "iss" claim).
                                                	Issuers map[string]*Issuer
                                                
                                                	// The HTTP client used to fetch issuer's JWKS. By default, doesn't follow any redirections.
                                                	HttpClient *http.Client
                                                }

                                                  Verifier can verify a JWT validity. Don't create verifiers directly, use jwit.NewVerifier* helpers instead.

                                                  func NewVerifier

                                                  func NewVerifier(issuers ...*Issuer) (*Verifier, error)

                                                    New creates a new JWIT Verifier given a set of truster issuers.

                                                    func (*Verifier) VerifyJWT

                                                    func (verifier *Verifier) VerifyJWT(rawJWT string, dest ...interface{}) (bool, error)

                                                      VerifyJWT verifies whether the provided raw JWT is valid and was signed using any of the public keys that are known from the JWT issuer. If the JWT is valid (i.e. comes from a known issuer, was signed by any of the known issuer's public keys and is under its validity period), true is returned. If it is valid and dest is non-nil, the JWT claims are unmarshalled into dest. If the JWT doesn't come from a known issuer, ErrUnknownIssuer is returned.

                                                      func (*Verifier) VerifyJWTWithKeys

                                                      func (verifier *Verifier) VerifyJWTWithKeys(
                                                      	rawJWT string,
                                                      	publicKeys []crypto.PublicKey,
                                                      	dest ...interface{},
                                                      ) (bool, error)

                                                        VerifyJWTWithKeys verifies whether the provided raw JWT is valid and was signed using any of the provided public keys. If the JWT is valid (i.e. comes from a known issuer, was signed by any of the provided public keys and is under its validity period), true is returned. If it is valid and dest is non-nil, the JWT claims are unmarshalled into dest.