223 lines
7.4 KiB
Markdown
223 lines
7.4 KiB
Markdown
|
# JWT [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/jwt.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwt)
|
||
|
|
||
|
Package jwt implements JSON Web Tokens as described in [RFC7519](https://tools.ietf.org/html/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
|
||
|
* Extra support for OpenID tokens via [github.com/lestrrat-go/jwx/jwt/openid](./jwt/openid)
|
||
|
|
||
|
More examples are located in the examples directory ([jwt_example_test.go](../examples/jwt_example_test.go))
|
||
|
|
||
|
# SYNOPSIS
|
||
|
|
||
|
## Verify a signed JWT
|
||
|
|
||
|
```go
|
||
|
token, err := jwt.Parse(bytes.NewReader(payload), jwt.WithKeySet(keyset))
|
||
|
if err != nil {
|
||
|
fmt.Printf("failed to parse payload: %s\n", err)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Token Usage
|
||
|
|
||
|
```go
|
||
|
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.WithToken(openid.New())`. If you need to use other specialized
|
||
|
claims, use `jwt.WithToken()` to specify the exact token type
|
||
|
|
||
|
```go
|
||
|
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.WithToken(openid.New())
|
||
|
if err != nil {
|
||
|
fmt.Printf("failed to parse JSON: %s\n", err)
|
||
|
return
|
||
|
}
|
||
|
if _, ok := t2.(openid.Token); !ok {
|
||
|
fmt.Printf("using jwt.WithToken(openid.New()) 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:
|
||
|
|
||
|
```go
|
||
|
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:
|
||
|
|
||
|
```go
|
||
|
// 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:
|
||
|
|
||
|
```go
|
||
|
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{}:
|
||
|
|
||
|
```go
|
||
|
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:
|
||
|
|
||
|
```go
|
||
|
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.
|
||
|
|
||
|
```go
|
||
|
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.
|