diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 984ab2da..8d4024ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - go: [1.16, 1.17, 1.18] + go: [1.17, 1.18, 1.19] steps: - name: Checkout uses: actions/checkout@v3 diff --git a/README.md b/README.md index f5d551ca..30f2f2a6 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ import "github.com/golang-jwt/jwt/v4" See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt/v4) for examples of usage: -* [Simple example of parsing and validating a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-Parse-Hmac) -* [Simple example of building and signing a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-New-Hmac) -* [Directory of Examples](https://pkg.go.dev/github.com/golang-jwt/jwt#pkg-examples) +* [Simple example of parsing and validating a token](https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac) +* [Simple example of building and signing a token](https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-New-Hmac) +* [Directory of Examples](https://pkg.go.dev/github.com/golang-jwt/jwt/v4#pkg-examples) ## Extensions @@ -96,7 +96,7 @@ A token is simply a JSON object that is signed by its author. this tells you exa * The author of the token was in the possession of the signing secret * The data has not been modified since it was signed -It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library. +It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. The companion project https://github.com/golang-jwt/jwe aims at a (very) experimental implementation of the JWE standard. ### Choosing a Signing Method @@ -110,10 +110,10 @@ Asymmetric signing methods, such as RSA, use different keys for signing and veri Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones: -* The [HMAC signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation -* The [RSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation -* The [ECDSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation -* The [EdDSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodEd25519) (`Ed25519`) expect `ed25519.PrivateKey` for signing and `ed25519.PublicKey` for validation +* The [HMAC signing method](https://pkg.go.dev/github.com/golang-jwt/jwt/v4#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation +* The [RSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt/v4#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation +* The [ECDSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt/v4#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation +* The [EdDSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt/v4#SigningMethodEd25519) (`Ed25519`) expect `ed25519.PrivateKey` for signing and `ed25519.PublicKey` for validation ### JWT and OAuth @@ -131,7 +131,7 @@ This library uses descriptive error messages whenever possible. If you are not g ## More -Documentation can be found [on pkg.go.dev](https://pkg.go.dev/github.com/golang-jwt/jwt). +Documentation can be found [on pkg.go.dev](https://pkg.go.dev/github.com/golang-jwt/jwt/v4). The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation. diff --git a/claims.go b/claims.go index 9d95cad2..364cec87 100644 --- a/claims.go +++ b/claims.go @@ -265,9 +265,5 @@ func verifyIss(iss string, cmp string, required bool) bool { if iss == "" { return !required } - if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 { - return true - } else { - return false - } + return subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 } diff --git a/cmd/jwt/main.go b/cmd/jwt/main.go index 6dfdaa8b..2ca6488d 100644 --- a/cmd/jwt/main.go +++ b/cmd/jwt/main.go @@ -3,7 +3,8 @@ // // Example usage: // The following will create and sign a token, then verify it and output the original claims. -// echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify - +// +// echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify - package main import ( diff --git a/http_example_test.go b/http_example_test.go index d15d7d5d..de3cbab4 100644 --- a/http_example_test.go +++ b/http_example_test.go @@ -73,7 +73,7 @@ type CustomerInfo struct { } type CustomClaimsExample struct { - *jwt.RegisteredClaims + jwt.RegisteredClaims TokenType string CustomerInfo } @@ -142,7 +142,7 @@ func createToken(user string) (string, error) { // set our claims t.Claims = &CustomClaimsExample{ - &jwt.RegisteredClaims{ + jwt.RegisteredClaims{ // set the expire time // see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4 ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 1)), diff --git a/parser.go b/parser.go index 2f61a69d..c0a6f692 100644 --- a/parser.go +++ b/parser.go @@ -42,6 +42,13 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc) } +// ParseWithClaims parses, validates, and verifies like Parse, but supplies a default object implementing the Claims +// interface. This provides default values which can be overridden and allows a caller to use their own type, rather +// than the default MapClaims implementation of Claims. +// +// Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims), +// make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the +// proper memory for it before passing in the overall claims, otherwise you might run into a panic. func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { token, parts, err := p.ParseUnverified(tokenString, claims) if err != nil { diff --git a/request/extractor.go b/request/extractor.go index 1dbc59a6..57de8b77 100644 --- a/request/extractor.go +++ b/request/extractor.go @@ -3,6 +3,7 @@ package request import ( "errors" "net/http" + "strings" ) // Errors @@ -79,3 +80,18 @@ func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) { return "", err } } + +// BearerExtractor extracts a token from the Authorization header. +// The header is expected to match the format "Bearer XX", where "XX" is the +// JWT token. +type BearerExtractor struct{} + +func (e BearerExtractor) ExtractToken(req *http.Request) (string, error) { + tokenHeader := req.Header.Get("Authorization") + // The usual convention is for "Bearer" to be title-cased. However, there's no + // strict rule around this, and it's best to follow the robustness principle here. + if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), "bearer ") { + return "", ErrNoTokenInRequest + } + return tokenHeader[7:], nil +} diff --git a/request/extractor_test.go b/request/extractor_test.go index e3bbb0a3..5be2b5f3 100644 --- a/request/extractor_test.go +++ b/request/extractor_test.go @@ -89,3 +89,23 @@ func makeExampleRequest(method, path string, headers map[string]string, urlArgs } return r } + +func TestBearerExtractor(t *testing.T) { + request := makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearer ToKen"}, nil) + token, err := BearerExtractor{}.ExtractToken(request) + if err != nil || token != "ToKen" { + t.Errorf("ExtractToken did not return token, returned: %v, %v", token, err) + } + + request = makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearo ToKen"}, nil) + token, err = BearerExtractor{}.ExtractToken(request) + if err == nil || token != "" { + t.Errorf("ExtractToken did not return error, returned: %v, %v", token, err) + } + + request = makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "BeArEr HeLO"}, nil) + token, err = BearerExtractor{}.ExtractToken(request) + if err != nil || token != "HeLO" { + t.Errorf("ExtractToken did not return token, returned: %v, %v", token, err) + } +} diff --git a/token.go b/token.go index 3cb0f3f0..71e909ea 100644 --- a/token.go +++ b/token.go @@ -99,6 +99,11 @@ func Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token return NewParser(options...).Parse(tokenString, keyFunc) } +// ParseWithClaims is a shortcut for NewParser().ParseWithClaims(). +// +// Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims), +// make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the +// proper memory for it before passing in the overall claims, otherwise you might run into a panic. func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error) { return NewParser(options...).ParseWithClaims(tokenString, claims, keyFunc) }