jwt

package
Version: v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Jun 1, 2021 License: MIT Imports: 26 Imported by: 107

README

github.com/lestrrat-go/jwx/jwt Go Reference

Package jwt implements JSON Web Tokens as described in RFC7519.

  • Convenience methods for oft-used keys ("aud", "sub", "iss", etc)
  • Convenience functions to extract/parse from http.Request, http.Header, url.Values
  • Ability to Get/Set arbitrary keys
  • Conversion to and from JSON
  • Generate signed tokens
  • Verify signed tokens

SYNOPSIS

More examples are located in the examples directory (jwt_example_test.go)

Verify a signed JWT

  token, err := jwt.Parse(bytes.NewReader(payload), jwt.WithKeySet(keyset))
  if err != nil {
    fmt.Printf("failed to parse payload: %s\n", err)
  }

Token Usage

func ExampleJWT() {
  const aLongLongTimeAgo = 233431200

  t := jwt.New()
  t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/jwt`)
  t.Set(jwt.AudienceKey, `Golang Users`)
  t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0))
  t.Set(`privateClaimKey`, `Hello, World!`)

  buf, err := json.MarshalIndent(t, "", "  ")
  if err != nil {
    fmt.Printf("failed to generate JSON: %s\n", err)
    return
  }

  fmt.Printf("%s\n", buf)
  fmt.Printf("aud -> '%s'\n", t.Audience())
  fmt.Printf("iat -> '%s'\n", t.IssuedAt().Format(time.RFC3339))
  if v, ok := t.Get(`privateClaimKey`); ok {
    fmt.Printf("privateClaimKey -> '%s'\n", v)
  }
  fmt.Printf("sub -> '%s'\n", t.Subject())

  key, err := rsa.GenerateKey(rand.Reader, 2048)
  if err != nil {
    log.Printf("failed to generate private key: %s", err)
    return
  }

  {
    // Signing a token (using raw rsa.PrivateKey)
    signed, err := jwt.Sign(t, jwa.RS256, key)
    if err != nil {
      log.Printf("failed to sign token: %s", err)
      return
    }
    _ = signed
  }

  {
    // Signing a token (using JWK)
    jwkKey, err := jwk.New(key)
    if err != nil {
      log.Printf("failed to create JWK key: %s", err)
      return
    }

    signed, err := jwt.Sign(t, jwa.RS256, jwkKey)
    if err != nil {
      log.Printf("failed to sign token: %s", err)
      return
    }
    _ = signed
  }
}

OpenID Claims

jwt package can work with token types other than the default one. For OpenID claims, use the token created by openid.New(), or use the jwt.WithOpenIDClaims(). If you need to use other specialized claims, use jwt.WithToken() to specify the exact token type

func Example_openid() {
  const aLongLongTimeAgo = 233431200

  t := openid.New()
  t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/jwt`)
  t.Set(jwt.AudienceKey, `Golang Users`)
  t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0))
  t.Set(`privateClaimKey`, `Hello, World!`)

  addr := openid.NewAddress()
  addr.Set(openid.AddressPostalCodeKey, `105-0011`)
  addr.Set(openid.AddressCountryKey, `日本`)
  addr.Set(openid.AddressRegionKey, `東京都`)
  addr.Set(openid.AddressLocalityKey, `港区`)
  addr.Set(openid.AddressStreetAddressKey, `芝公園 4-2-8`)
  t.Set(openid.AddressKey, addr)

  buf, err := json.MarshalIndent(t, "", "  ")
  if err != nil {
    fmt.Printf("failed to generate JSON: %s\n", err)
    return
  }
  fmt.Printf("%s\n", buf)

  t2, err := jwt.ParseBytes(buf, jwt.WithOpenIDClaims())
  if err != nil {
    fmt.Printf("failed to parse JSON: %s\n", err)
    return
  }
  if _, ok := t2.(openid.Token); !ok {
    fmt.Printf("using jwt.WithOpenIDClaims() creates an openid.Token instance")
    return
  }
}

FAQ

Why is jwt.Token an interface?

In this package, jwt.Token is an interface. This is not an arbitrary choice: there are actual reason for the type being an interface.

We understand that if you are migrating from another library this may be a deal breaker, but we hope you can at least appreciate the fact that this was not done arbitrarily, and that there were real technical trade offs that were evaluated.

No uninitialized tokens

First and foremost, by making it an interface, you cannot use an uninitialized token:

var token1 jwt.Token // this is nil, you can't just start using this
if err := json.Unmarshal(data, &token1); err != nil { // so you can't do this
   ...
}

// But you _can_ do this, and we _want_ you to do this so the object is properly initialized
token2 = jwt.New()
if err := json.Unmarshal(data, &token2); err != nil { // actually, in practice you should use jwt.Parse()
   ....
}
But why does it need to be initialized?

There are several reasons, but one of the reasons is that I'm using a sync.Mutex to avoid races. We want this to be properly initialized.

The other reason is that we support custom claims out of the box. The map[string]interface{} container is initialized during new. This is important when checking for equality using reflect-y methods (akin to reflect.DeepEqual), because if you allowed zero values, you could end up with "empty" tokens, that actually differ. Consider the following:

// assume jwt.Token was s struct, not an interface
token1 := jwt.Token{ privateClaims: make(map[string]interface{}) }
token2 := jwt.Token{ privateClaims: nil }

These are semantically equivalent, but users would need to be aware of this difference when comparing values. By forcing the user to use a constructor, we can force a uniform empty state.

Standard way to store values

Unlike some other libraries, this library allows you to store standard claims and non-standard claims in the same token.

You want to store standard claims in a properly typed field, which we do for fields like "iss", "nbf", etc. But for non-standard claims, there is just no way of doing this, so we have to use a container like map[string]interface{}

This means that if you allow direct access to these fields via a struct, you will have two different ways to access the claims, which is confusing:

tok.Issuer = ...
tok.PrivateClaims["foo"] = ...

So we want to hide where this data is stored, and use a standard method like Set() and Get() to store all the values. At this point you are effectively going to hide the implementation detail from the user, so you end up with a struct like below, which is fundamentally not so different from providing just an interface{}:

type Token struct {
  // unexported fields
}

func (tok *Token) Set(...) { ... }
Use of pointers to store values

We wanted to differentiate the state between a claim being uninitialized, and a claim being initialized to empty.

So we use pointers to store values:

type stdToken struct {
  ....
  issuer *string // if nil, uninitialized. if &(""), initialized to empty
}

This is fine for us, but we doubt that this would be something users would want to do. This is a subtle difference, but cluttering up the API with slight variations of the same type (i.e. pointers vs non-pointers) seemed like a bad idea to us.

token.Issuer = &issuer // want to avoid this

token.Set(jwt.IssuerKey, "foobar") // so this is what we picked

This way users no longer need to care how the data is internally stored.

Allow more than one type of token through the same interface

dgrijalva/jwt-go does this in a different way, but we felt that it would be more intuitive for all tokens to follow a single interface so there is fewer type conversions required.

See the openid token for an example.

Documentation

Overview

Package jwt implements JSON Web Tokens as described in https://tools.ietf.org/html/rfc7519

Index

Constants

View Source
const (
	AudienceKey   = "aud"
	ExpirationKey = "exp"
	IssuedAtKey   = "iat"
	IssuerKey     = "iss"
	JwtIDKey      = "jti"
	NotBeforeKey  = "nbf"
	SubjectKey    = "sub"
)

Variables

This section is empty.

Functions

func Equal

func Equal(t1, t2 Token) bool

Equal compares two JWT tokens. Do not use `reflect.Equal` or the like to compare tokens as they will also compare extra detail such as sync.Mutex objects used to control concurrent access.

The comparison for values is currently done using a simple equality ("=="), except for time.Time, which uses time.Equal after dropping the monotonic clock and truncating the values to 1 second accuracy.

if both t1 and t2 are nil, returns true

func RegisterCustomField

func RegisterCustomField(name string, object interface{})

RegisterCustomField allows users to specify that a private field be decoded as an instance of the specified type. This option has a global effect.

For example, suppose you have a custom field `x-birthday`, which you want to represent as a string formatted in RFC3339 in JSON, but want it back as `time.Time`.

In that case you would register a custom field as follows

jwt.RegisterCustomField(`x-birthday`, timeT)

Then `token.Get("x-birthday")` will still return an `interface{}`, but you can convert its type to `time.Time`

bdayif, _ := token.Get(`x-birthday`)
bday := bdayif.(time.Time)

func Settings

func Settings(options ...GlobalOption)

Settings controls global settings that are specific to JWTs.

func Sign

func Sign(t Token, alg jwa.SignatureAlgorithm, key interface{}, options ...Option) ([]byte, error)

Sign is a convenience function to create a signed JWT token serialized in compact form.

It accepts either a raw key (e.g. rsa.PrivateKey, ecdsa.PrivateKey, etc) or a jwk.Key, and the name of the algorithm that should be used to sign the token.

If the key is a jwk.Key and the key contains a key ID (`kid` field), then it is added to the protected header generated by the signature

The algorithm specified in the `alg` parameter must be able to support the type of key you provided, otherwise an error is returned.

The protected header will also automatically have the `typ` field set to the literal value `JWT`, unless you provide a custom value for it by jwt.WithHeaders option.

func Validate

func Validate(t Token, options ...ValidateOption) error

Validate makes sure that the essential claims stand.

See the various `WithXXX` functions for optional parameters that can control the behavior of this method.

Types

type ClaimPair

type ClaimPair = mapiter.Pair

type Clock

type Clock interface {
	Now() time.Time
}

type ClockFunc

type ClockFunc func() time.Time

func (ClockFunc) Now

func (f ClockFunc) Now() time.Time

type DecodeCtx

type DecodeCtx = json.DecodeCtx

type GlobalOption

type GlobalOption interface {
	Option
	// contains filtered or unexported methods
}

GlobalOption describes an Option that can be passed to `Settings()`.

func WithFlattenAudience

func WithFlattenAudience(v bool) GlobalOption

WithFlattenAudience specifies if the "aud" claim should be flattened to a single string upon the token being serialized to JSON.

This is sometimes important when a JWT consumer does not understand that the "aud" claim can actually take the form of an array of strings.

The default value is `false`, which means that "aud" claims are always rendered as a arrays of strings. This setting has a global effect, and will change the behavior for all JWT serialization.

type Iterator

type Iterator = mapiter.Iterator

type Option

type Option = option.Interface

type ParseOption

type ParseOption interface {
	ReadFileOption
	// contains filtered or unexported methods
}

ParseOption describes an Option that can be passed to `Parse()`. ParseOption also implements ReadFileOption, therefore it may be safely pass them to `jwt.ReadFile()`

func UseDefaultKey

func UseDefaultKey(value bool) ParseOption

UseDefaultKey is used in conjunction with the option WithKeySet to instruct the Parse method to default to the single key in a key set when no Key ID is included in the JWT. If the key set contains multiple keys then the behaviour is unchanged.

func WithHeaders

func WithHeaders(hdrs jws.Headers) ParseOption

WithHeaders is passed to `Sign()` method, to allow specifying arbitrary header values to be included in the header section of the jws message

func WithKeySet

func WithKeySet(set jwk.Set) ParseOption

WithKeySet forces the Parse method to verify the JWT message using one of the keys in the given key set. The key to be used is chosen by matching the Key ID of the JWT and the ID of the given keys.

When using this option, keys MUST have a properly 'alg' field set. This is because we need to know the exact algorithm that you (the user) wants to use to verify the token. We do NOT trust the token's headers, because they can easily be tampered with.

func WithToken

func WithToken(t Token) ParseOption

WithToken specifies the token instance that is used when parsing JWT tokens.

func WithTypedClaim

func WithTypedClaim(name string, object interface{}) ParseOption

WithTypedClaim allows a private claim to be parsed into the object type of your choice. It works much like the RegisterCustomField, but the effect is only applicable to the jwt.Parse function call which receives this option.

While this can be extremely useful, this option should be used with caution: There are many caveats that your entire team/user-base needs to be aware of, and therefore in general its use is discouraged. Only use it when you know what you are doing, and you document its use clearly for others.

First and foremost, this is a "per-object" option. Meaning that given the same serialized format, it is possible to generate two objects whose internal representations may differ. That is, if you parse one _WITH_ the option, and the other _WITHOUT_, their internal representation may completely differ. This could potentially lead to problems.

Second, specifying this option will slightly slow down the decoding process as it needs to consult multiple definitions sources (global and local), so be careful if you are decoding a large number of tokens, as the effects will stack up.

Finally, this option will also NOT work unless the tokens themselves support such parsing mechanism. For example, while tokens obtained from `jwt.New()` and `openid.New()` will respect this option, if you provide your own custom token type, it will need to implement the TokenWithDecodeCtx interface.

func WithValidate

func WithValidate(b bool) ParseOption

WithValidate is passed to `Parse()` method to denote that the validation of the JWT token should be performed after a successful parsing of the incoming payload.

func WithVerify

func WithVerify(alg jwa.SignatureAlgorithm, key interface{}) ParseOption

WithVerify forces the Parse method to verify the JWT message using the given key. XXX Should have been named something like WithVerificationKey

type ParseRequestOption

type ParseRequestOption interface {
	ParseOption
	// contains filtered or unexported methods
}

ParseRequestOption describes an Option that can be passed to `ParseRequest()`.

func WithFormKey

func WithFormKey(v string) ParseRequestOption

WithFormKey is used to specify header keys to search for tokens.

While the type system allows this option to be passed to jwt.Parse() directly, doing so will have no effect. Only use it for HTTP request parsing functions

func WithHeaderKey

func WithHeaderKey(v string) ParseRequestOption

WithHeaderKey is used to specify header keys to search for tokens.

While the type system allows this option to be passed to jwt.Parse() directly, doing so will have no effect. Only use it for HTTP request parsing functions

type ReadFileOption

type ReadFileOption interface {
	Option
	// contains filtered or unexported methods
}

ReadFileOption describes options that can be passed to ReadFile.

type Token

type Token interface {
	Audience() []string
	Expiration() time.Time
	IssuedAt() time.Time
	Issuer() string
	JwtID() string
	NotBefore() time.Time
	Subject() string
	PrivateClaims() map[string]interface{}
	Get(string) (interface{}, bool)
	Set(string, interface{}) error
	Remove(string) error
	Clone() (Token, error)
	Iterate(context.Context) Iterator
	Walk(context.Context, Visitor) error
	AsMap(context.Context) (map[string]interface{}, error)
}

Token represents a generic JWT token. which are type-aware (to an extent). Other claims may be accessed via the `Get`/`Set` methods but their types are not taken into consideration at all. If you have non-standard claims that you must frequently access, consider creating accessors functions like the following

func SetFoo(tok jwt.Token) error func GetFoo(tok jwt.Token) (*Customtyp, error)

Embedding jwt.Token into another struct is not recommended, becase jwt.Token needs to handle private claims, and this really does not work well when it is embedded in other structure

func New

func New() Token

New creates a standard token, with minimal knowledge of possible claims. Standard claims include"aud", "exp", "iat", "iss", "jti", "nbf" and "sub". Convenience accessors are provided for these standard claims

func Parse

func Parse(s []byte, options ...ParseOption) (Token, error)

Parse parses the JWT token payload and creates a new `jwt.Token` object. The token must be encoded in either JSON format or compact format.

If the token is signed and you want to verify the payload matches the signature, you must pass the jwt.WithVerify(alg, key) or jwt.WithKeySet(jwk.Set) option. If you do not specify these parameters, no verification will be performed.

If you also want to assert the validity of the JWT itself (i.e. expiration and such), use the `Validate()` function on the returned token, or pass the `WithValidate(true)` option. Validate options can also be passed to `Parse`

This function takes both ParseOption and ValidateOption types: ParseOptions control the parsing behavior, and ValidateOptions are passed to `Validate()` when `jwt.WithValidate` is specified.

func ParseForm

func ParseForm(values url.Values, name string, options ...ParseOption) (Token, error)

ParseForm parses a JWT stored in a url.Value.

func ParseHeader

func ParseHeader(hdr http.Header, name string, options ...ParseOption) (Token, error)

ParseHeader parses a JWT stored in a http.Header.

For the header "Authorization", it will strip the prefix "Bearer " and will treat the remaining value as a JWT.

func ParseReader

func ParseReader(src io.Reader, options ...ParseOption) (Token, error)

ParseReader calls Parse against an io.Reader

func ParseRequest

func ParseRequest(req *http.Request, options ...ParseOption) (Token, error)

ParseRequest searches a http.Request object for a JWT token.

Specifying WithHeaderKey() will tell it to search under a specific header key. Specifying WithFormKey() will tell it to search under a specific form field.

By default, "Authorization" header will be searched.

If WithHeaderKey() is used, you must explicitly re-enable searching for "Authorization" header.

# searches for "Authorization"
jwt.ParseRequest(req)

# searches for "x-my-token" ONLY.
jwt.ParseRequest(req, http.WithHeaderKey("x-my-token"))

# searches for "Authorization" AND "x-my-token"
jwt.ParseRequest(req, http.WithHeaderKey("Authorization"), http.WithHeaderKey("x-my-token"))

func ParseString

func ParseString(s string, options ...ParseOption) (Token, error)

ParseString calls Parse against a string

func ReadFile

func ReadFile(path string, options ...ReadFileOption) (Token, error)

type TokenWithDecodeCtx

type TokenWithDecodeCtx = json.DecodeCtxContainer

type ValidateOption

type ValidateOption interface {
	ParseOption
	// contains filtered or unexported methods
}

ValidateOption describes an Option that can be passed to Validate(). ValidateOption also implements ParseOption, therefore it may be safely passed to `Parse()` (and thus `jwt.ReadFile()`)

func WithAcceptableSkew

func WithAcceptableSkew(dur time.Duration) ValidateOption

WithAcceptableSkew specifies the duration in which exp and nbf claims may differ by. This value should be positive

func WithAudience

func WithAudience(s string) ValidateOption

WithAudience specifies that expected audience value. `Validate()` will return true if one of the values in the `aud` element matches this value. If not specified, the value of issuer is not verified at all.

func WithClaimValue

func WithClaimValue(name string, v interface{}) ValidateOption

WithClaimValue specifies that expected any claim value.

func WithClock

func WithClock(c Clock) ValidateOption

WithClock specifies the `Clock` to be used when verifying claims exp and nbf.

func WithIssuer

func WithIssuer(s string) ValidateOption

WithIssuer specifies that expected issuer value. If not specified, the value of issuer is not verified at all.

func WithJwtID

func WithJwtID(s string) ValidateOption

WithJwtID specifies that expected jti value. If not specified, the value of jti is not verified at all.

func WithSubject

func WithSubject(s string) ValidateOption

WithSubject specifies that expected subject value. If not specified, the value of subject is not verified at all.

type VerifyParameters

type VerifyParameters interface {
	Algorithm() jwa.SignatureAlgorithm
	Key() interface{}
}

type Visitor

type Visitor = iter.MapVisitor

type VisitorFunc

type VisitorFunc = iter.MapVisitorFunc

Directories

Path Synopsis
Package openid provides a specialized token that provides utilities to work with OpenID JWT tokens.
Package openid provides a specialized token that provides utilities to work with OpenID JWT tokens.
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to