package jwt

import "github.com/hashicorp/cap/jwt"

Package jwt provides signature verification and claims set validation for JSON Web Tokens (JWT) of the JSON Web Signature (JWS) form.

JWT claims set validation provided by the package includes the option to validate all registered claim names defined in https://tools.ietf.org/html/rfc7519#section-4.1.

JOSE header validation provided by the the package includes the option to validate the "alg" (Algorithm) Header Parameter defined in https://tools.ietf.org/html/rfc7515#section-4.1.

JWT signature verification is supported by providing keys from the following sources:

JWT signature verification supports the following asymmetric algorithms as defined in https://www.rfc-editor.org/rfc/rfc7518.html#section-3.1:

Index

Examples

Constants

const DefaultLeewaySeconds = 150

DefaultLeewaySeconds defines the amount of leeway that's used by default for validating the "nbf" (Not Before) and "exp" (Expiration Time) claims.

Functions

func ParsePublicKeyPEM

func ParsePublicKeyPEM(data []byte) (crypto.PublicKey, error)

ParsePublicKeyPEM is used to parse RSA and ECDSA public keys from PEMs. The given data must be of PEM-encoded x509 certificate or PKIX public key forms. It returns an *rsa.PublicKey or *ecdsa.PublicKey.

func SupportedSigningAlgorithm

func SupportedSigningAlgorithm(algs ...Alg) error

SupportedSigningAlgorithm returns an error if any of the given Algs are not supported signing algorithms.

Types

type Alg

type Alg string

Alg represents asymmetric signing algorithms

const (
	// JOSE asymmetric signing algorithm values as defined by RFC 7518.
	//
	// See: https://tools.ietf.org/html/rfc7518#section-3.1
	RS256 Alg = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
	RS384 Alg = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
	RS512 Alg = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
	ES256 Alg = "ES256" // ECDSA using P-256 and SHA-256
	ES384 Alg = "ES384" // ECDSA using P-384 and SHA-384
	ES512 Alg = "ES512" // ECDSA using P-521 and SHA-512
	PS256 Alg = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
	PS384 Alg = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
	PS512 Alg = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
	EdDSA Alg = "EdDSA" // Ed25519 using SHA-512
)

type Expected

type Expected struct {
	// The expected JWT "iss" (issuer) claim value. If empty, validation is skipped.
	Issuer string

	// The expected JWT "sub" (subject) claim value. If empty, validation is skipped.
	Subject string

	// The expected JWT "jti" (JWT ID) claim value. If empty, validation is skipped.
	ID string

	// The list of expected JWT "aud" (audience) claim values to match against.
	// The JWT claim will be considered valid if it matches any of the expected
	// audiences. If empty, validation is skipped.
	Audiences []string

	// SigningAlgorithms provides the list of expected JWS "alg" (algorithm) header
	// parameter values to match against. The JWS header parameter will be considered
	// valid if it matches any of the expected signing algorithms. The following
	// algorithms are supported: RS256, RS384, RS512, ES256, ES384, ES512, PS256,
	// PS384, PS512, EdDSA. If empty, defaults to RS256.
	SigningAlgorithms []Alg

	// NotBeforeLeeway provides the option to set an amount of leeway to use when
	// validating the "nbf" (Not Before) claim. If the duration is zero or not
	// provided, a default leeway of 150 seconds will be used. If the duration is
	// negative, no leeway will be used.
	NotBeforeLeeway time.Duration

	// ExpirationLeeway provides the option to set an amount of leeway to use when
	// validating the "exp" (Expiration Time) claim. If the duration is zero or not
	// provided, a default leeway of 150 seconds will be used. If the duration is
	// negative, no leeway will be used.
	ExpirationLeeway time.Duration

	// ClockSkewLeeway provides the option to set an amount of leeway to use when
	// validating the "nbf" (Not Before), "exp" (Expiration Time), and "iat" (Issued At)
	// claims. If the duration is zero or not provided, a default leeway of 60 seconds
	// will be used. If the duration is negative, no leeway will be used.
	ClockSkewLeeway time.Duration

	// Now provides the option to specify a func for determining what the current time is.
	// The func will be used to provide the current time when validating a JWT with respect to
	// the "nbf" (Not Before), "exp" (Expiration Time), and "iat" (Issued At) claims. If not
	// provided, defaults to returning time.Now().
	Now func() time.Time
}

Expected defines the expected claims values to assert when validating a JWT. For claims that involve validation of the JWT with respect to time, leeway fields are provided to account for potential clock skew.

type KeySet

type KeySet interface {
	// VerifySignature parses the given JWT, verifies its signature, and returns the claims in its payload.
	// The given JWT must be of the JWS compact serialization form.
	VerifySignature(ctx context.Context, token string) (claims map[string]interface{}, err error)
}

KeySet represents a set of keys that can be used to verify the signatures of JWTs. A KeySet is expected to be backed by a set of local or remote keys.

func NewJSONWebKeySet

func NewJSONWebKeySet(ctx context.Context, jwksURL string, jwksCAPEM string) (KeySet, error)

NewJSONWebKeySet returns a KeySet that verifies JWT signatures using keys from the JSON Web Key Set (JWKS) at the given jwksURL. The client used to obtain the remote JWKS will verify server certificates using the root certificates provided by jwksCAPEM. If jwksCAPEM is not provided, system certificates are used.

Example

Code:play 

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/hashicorp/cap/jwt"
)

func main() {
	ctx := context.Background()

	keySet, err := jwt.NewJSONWebKeySet(ctx, "your_jwks_url", "your_jwks_ca_pem")
	if err != nil {
		log.Fatal(err)
	}

	token := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJleHBfaXNzIiwiZXhwIjoxNTI2MjM5MDIyfQ.XG1xYJcuPMfgu8xkMzVjkYK2WIUyl4-A1Zq1j4Dfr99-PJUN36ZAgi8Fj08modiexXETrg05MqSxkJAE5Czns1IhqEEypx6xfYHSINp0SLKxBFHPA4BCi0IW83T-e225JjjVEGFR_Wo8QM6Rc-qQVJ9bqwKD4kcbQeMACkgGFcgNurtNkOM9vtOEs0Pe9tb4nHYw4ef1stCytTi9GFZwGoHQf0pjpWCpjlxaFIR4vmHQ4YB3w29o_tKN6zqyA2FITnvkzGnaLvdPecJNskRSCPUTRfYcVVNXCOnCvTdpvwK-c4nCs5yGnw3eeFoT6mhQSp39KYti1MpHNQTYwZrLTA"
	claims, err := keySet.VerifySignature(ctx, token)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(claims)
}

func NewOIDCDiscoveryKeySet

func NewOIDCDiscoveryKeySet(ctx context.Context, issuer string, issuerCAPEM string) (KeySet, error)

NewOIDCDiscoveryKeySet returns a KeySet that verifies JWT signatures using keys from the JSON Web Key Set (JWKS) published in the discovery document at the given issuer URL. The client used to obtain the remote keys will verify server certificates using the root certificates provided by issuerCAPEM. If issuerCAPEM is not provided, system certificates are used.

Example

Code:play 

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/hashicorp/cap/jwt"
)

func main() {
	ctx := context.Background()

	keySet, err := jwt.NewOIDCDiscoveryKeySet(ctx, "your_issuer_url", "your_issuer_ca_pem")
	if err != nil {
		log.Fatal(err)
	}

	token := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJleHBfaXNzIiwiZXhwIjoxNTI2MjM5MDIyfQ.XG1xYJcuPMfgu8xkMzVjkYK2WIUyl4-A1Zq1j4Dfr99-PJUN36ZAgi8Fj08modiexXETrg05MqSxkJAE5Czns1IhqEEypx6xfYHSINp0SLKxBFHPA4BCi0IW83T-e225JjjVEGFR_Wo8QM6Rc-qQVJ9bqwKD4kcbQeMACkgGFcgNurtNkOM9vtOEs0Pe9tb4nHYw4ef1stCytTi9GFZwGoHQf0pjpWCpjlxaFIR4vmHQ4YB3w29o_tKN6zqyA2FITnvkzGnaLvdPecJNskRSCPUTRfYcVVNXCOnCvTdpvwK-c4nCs5yGnw3eeFoT6mhQSp39KYti1MpHNQTYwZrLTA"
	claims, err := keySet.VerifySignature(ctx, token)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(claims)
}

func NewStaticKeySet

func NewStaticKeySet(publicKeys []crypto.PublicKey) (KeySet, error)

NewStaticKeySet returns a KeySet that verifies JWT signatures using the given publicKeys.

Example

Code:play 

package main

import (
	"context"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"fmt"
	"log"

	"github.com/hashicorp/cap/jwt"
)

func main() {
	ctx := context.Background()

	rsaKey, err := rsa.GenerateKey(rand.Reader, 4096)
	if err != nil {
		log.Fatal(err)
	}

	keys := []crypto.PublicKey{
		rsaKey.Public(),
	}

	keySet, err := jwt.NewStaticKeySet(keys)
	if err != nil {
		log.Fatal(err)
	}

	token := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJleHBfaXNzIiwiZXhwIjoxNTI2MjM5MDIyfQ.XG1xYJcuPMfgu8xkMzVjkYK2WIUyl4-A1Zq1j4Dfr99-PJUN36ZAgi8Fj08modiexXETrg05MqSxkJAE5Czns1IhqEEypx6xfYHSINp0SLKxBFHPA4BCi0IW83T-e225JjjVEGFR_Wo8QM6Rc-qQVJ9bqwKD4kcbQeMACkgGFcgNurtNkOM9vtOEs0Pe9tb4nHYw4ef1stCytTi9GFZwGoHQf0pjpWCpjlxaFIR4vmHQ4YB3w29o_tKN6zqyA2FITnvkzGnaLvdPecJNskRSCPUTRfYcVVNXCOnCvTdpvwK-c4nCs5yGnw3eeFoT6mhQSp39KYti1MpHNQTYwZrLTA"
	claims, err := keySet.VerifySignature(ctx, token)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(claims)
}

type Validator

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

Validator validates JSON Web Tokens (JWT) by providing signature verification and claims set validation. Validator can contain either a single or multiple KeySets and will attempt to verify the JWT by iterating through the configured KeySets.

func NewValidator

func NewValidator(keySets ...KeySet) (*Validator, error)

NewValidator returns a Validator that uses the given KeySet to verify JWT signatures.

func (*Validator) Validate

func (v *Validator) Validate(ctx context.Context, token string, expected Expected) (map[string]interface{}, error)

Validate validates JWTs of the JWS compact serialization form.

The given JWT is considered valid if:

  1. Its signature is successfully verified.
  2. Its claims set and header parameter values match what's given by Expected.
  3. It's valid with respect to the current time. This means that the current time must be within the times (inclusive) given by the "nbf" (Not Before) and "exp" (Expiration Time) claims and after the time given by the "iat" (Issued At) claim, with configurable leeway. See Expected.Now() for details on how the current time is provided for validation.
Example

Code:play 

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/hashicorp/cap/jwt"
)

func main() {
	ctx := context.Background()

	keySet, err := jwt.NewJSONWebKeySet(ctx, "your_jwks_url", "your_jwks_ca_pem")
	if err != nil {
		log.Fatal(err)
	}

	validator, err := jwt.NewValidator(keySet)
	if err != nil {
		log.Fatal(err)
	}

	expected := jwt.Expected{
		Issuer:            "your_expected_issuer",
		Subject:           "your_expected_subject",
		ID:                "your_expected_jwt_id",
		Audiences:         []string{"your_expected_audiences"},
		SigningAlgorithms: []jwt.Alg{jwt.RS256},
	}

	token := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJleHBfaXNzIiwiZXhwIjoxNTI2MjM5MDIyfQ.XG1xYJcuPMfgu8xkMzVjkYK2WIUyl4-A1Zq1j4Dfr99-PJUN36ZAgi8Fj08modiexXETrg05MqSxkJAE5Czns1IhqEEypx6xfYHSINp0SLKxBFHPA4BCi0IW83T-e225JjjVEGFR_Wo8QM6Rc-qQVJ9bqwKD4kcbQeMACkgGFcgNurtNkOM9vtOEs0Pe9tb4nHYw4ef1stCytTi9GFZwGoHQf0pjpWCpjlxaFIR4vmHQ4YB3w29o_tKN6zqyA2FITnvkzGnaLvdPecJNskRSCPUTRfYcVVNXCOnCvTdpvwK-c4nCs5yGnw3eeFoT6mhQSp39KYti1MpHNQTYwZrLTA"
	claims, err := validator.Validate(ctx, token, expected)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(claims)
}

func (*Validator) ValidateAllowMissingIatNbfExp

func (v *Validator) ValidateAllowMissingIatNbfExp(ctx context.Context, token string, expected Expected) (map[string]interface{}, error)

ValidateAllowMissingIatNbfExp validates JWTs of the JWS compact serialization form.

The given JWT is considered valid if:

  1. Its signature is successfully verified.
  2. Its claims set and header parameter values match what's given by Expected.
  3. It's valid with respect to the current time. This means that the current time must be within the times (inclusive) given by the "nbf" (Not Before) and "exp" (Expiration Time) claims and after the time given by the "iat" (Issued At) claim, with configurable leeway, if they are present. If all of "nbf", "exp", and "iat" are missing, then this check is skipped. See Expected.Now() for details on how the current time is provided for validation.

Source Files

algs.go docs.go jwt.go keyset.go

Version
v0.9.0 (latest)
Published
Feb 28, 2025
Platform
linux/amd64
Imports
20 packages
Last checked
2 months ago

Tools for package owners.