Compare commits

..

31 Commits

Author SHA1 Message Date
re 29918af7f7 fix repos 2022-12-12 17:07:02 +03:00
Alexander Yastrebov 9358574a7a
Allow strict base64 decoding (#259)
By default base64 decoder works in non-strict mode which
allows tweaking signatures having padding without failing validation.

This creates a potential problem if application treats token value as an identifier.

For example ES256 signature has length of 64 bytes and two padding symbols (stripped by default).
Therefore its base64-encoded value can only end with A, Q, g and w.
In non-strict mode last symbol could be tweaked resulting in 16 distinct
token values having the same signature and passing validation.

This change adds backward-compatible global config variable DecodeStrict
(similar to existing DecodePaddingAllowed) that enables strict base64 decoder mode.

See also https://github.com/golang/go/issues/15656.

Signed-off-by: Alexander Yastrebov <yastrebov.alex@gmail.com>
2022-12-09 18:04:03 +01:00
Christian Banse 2f0984a28b
Using `tparse` for nicer CI test display (#251) 2022-11-29 10:00:41 -05:00
Christian Banse 2101c1f4bc
No pointer embedding in the example (#255)
Fixes #223
2022-11-08 15:43:45 +01:00
Krouton 35053d4e20
Removed unneeded if statement (#241) 2022-10-15 14:38:07 +02:00
Jacob Kopczynski 0c4e387985
Add doc comment to ParseWithClaims (#232) 2022-09-26 10:01:52 -04:00
Christian Banse bfea432b1a
Include https://github.com/golang-jwt/jwe in README (#229) 2022-08-20 17:04:58 +02:00
Michael Fridman d81acbf7f3
Bump matrix to support latest go version (go1.19) (#231)
* Bump matrix to support latest go version (go1.19)

* Fix comment
2022-08-20 16:53:04 +02:00
Hugo fdaf0eb0e0
Implement a BearerExtractor (#226)
* Implement a BearerExtractor

This is a rather common extractor; it extracts the JWT from the HTTP
Authorization header, expecting it to include the "Bearer " prefix.

This patterns is rather common and this snippet is repeated in enough
applications that it's probably best to just include it upstream and
allow reusing it.

* Ignore case-sensitivity for "Bearer"
2022-08-19 13:59:36 +02:00
KroKite f2878bb94b
fix: link update for README.md for v4 (#217)
Co-authored-by: Christian Banse <oxisto@aybaze.com>
2022-08-15 12:45:52 +02:00
George Kechagias 9294af54b5
chore: remove unused claims in RSA table driven test (#212) 2022-06-04 08:03:41 -04:00
Qian Qiao 2da0bf7566
Fixed integer overflow in NumericDate.MarshalJSON (#200) 2022-06-03 22:13:34 -04:00
Christian Banse 8fb42696ff
Update SECURITY.md (#207) 2022-05-28 21:53:11 +02:00
Michael Fridman cf43decf7c
Create SECURITY.md (#171) 2022-05-28 12:40:34 -04:00
Michael Fridman 4426925f0c
CI check for Go code formatting (#206)
Signed-off-by: jay-dee7 <jasdeepsingh.uppal@gmail.com>
Co-authored-by: jay-dee7 <jasdeepsingh.uppal@gmail.com>
2022-05-28 16:03:15 +02:00
Håvard Anda Estensen f6c6299f67
chore: replace ioutil with io and os (#198) 2022-05-27 19:11:16 -04:00
Luigi Morel 89a6400b7f
add installation guidelines to the README file (#204) 2022-05-27 19:07:25 -04:00
Vladislav Polyakov 6e2ab4291f
docs: update link to pkg.go.dev page (#195) 2022-04-19 17:45:50 +02:00
Christian Banse 83478b3c8f
Added MicahParks/keyfunc to extensions (#194) 2022-04-18 22:01:59 +02:00
Michael Fridman 0972257eba
Revert "feat: port clockskew support (#139)" (#184)
This reverts commit d489c99d3e.
2022-03-26 10:13:03 -04:00
Michael Fridman 1096e506e6
Add go1.18 to ci pipeline (#173) 2022-03-18 07:15:45 -04:00
ksegun d489c99d3e
feat: port clockskew support (#139)
Co-authored-by: Kolawole Segun <Kolawole.Segun@kyndryl.com>
Co-authored-by: Christian Banse <oxisto@aybaze.com>
2022-03-08 08:43:46 +01:00
ydylla 6de17d3b3e
fix: expired token error message (#165) 2022-02-15 08:31:33 -05:00
Michael Fridman 279dd19720
Set json encoding precision (#162) 2022-02-09 21:54:31 -05:00
Giau. Tran Minh 863d23d08a
fix: fixed typo detect by cSpell (#164) 2022-02-09 13:14:42 -03:00
Michael Fridman 2387103809
Add JWT logo image attribution (#161) 2022-02-08 22:35:49 -05:00
Máté Lang d0c0939ff8
updated README.md to contain more extensions (#155)
* updated README.md to contain more extensions

* Update README.md

Co-authored-by: Luis Gabriel Gomez <lggomez@users.noreply.github.com>

Co-authored-by: Luis Gabriel Gomez <lggomez@users.noreply.github.com>
2022-02-03 08:49:22 -03:00
hyeonjae e01ed05a31
remove unnecessary for loop in token signing string for readability (#34)
* remove unnecessary for loop in token signing string for readability

 - add testcase
 - add benchmark
 - improve performance slightly

* Fix benchtests on token_test.go

* Update token_test.go to v4

Co-authored-by: hyeonjae <hyeonjae@ip-192-168-1-3.ap-northeast-2.compute.internal>
Co-authored-by: Luis Gabriel Gomez <lggomez@users.noreply.github.com>
2022-02-03 08:47:58 -03:00
Christian Banse 78a18c0808
Implementing `Is(err) bool` to support Go 1.13 style error checking (#136) 2022-01-19 22:55:19 +01:00
Stefan Tudose 0fb40d3824
use errors.Is for extractor errors (#141) 2021-12-15 12:50:05 +01:00
tfonfara c435f38291
#129: Added VerifyIssuer method to RegisteredClaims (#130) 2021-11-24 14:27:41 +01:00
33 changed files with 593 additions and 165 deletions

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- uses: reviewdog/action-staticcheck@v1 - uses: reviewdog/action-staticcheck@v1
with: with:
github_token: ${{ secrets.github_token }} github_token: ${{ secrets.github_token }}
@ -25,16 +25,26 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
go: [1.15, 1.16, 1.17] go: [1.17, 1.18, 1.19]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v3
with: with:
go-version: "${{ matrix.go }}" go-version: "${{ matrix.go }}"
check-latest: true
cache: true
- name: Check Go code formatting
run: |
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
gofmt -s -l .
echo "Please format Go code by running: go fmt ./..."
exit 1
fi
- name: Build - name: Build
run: | run: |
go install github.com/mfridman/tparse@latest
go vet ./... go vet ./...
go test -v ./... go test -v -race -count=1 -json -coverpkg=$(go list ./...) ./... | tparse -follow -notests
go build ./... go build ./...

View File

@ -2,18 +2,18 @@
Starting from [v4.0.0](https://github.com/golang-jwt/jwt/releases/tag/v4.0.0), the import path will be: Starting from [v4.0.0](https://github.com/golang-jwt/jwt/releases/tag/v4.0.0), the import path will be:
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
The `/v4` version will be backwards compatible with existing `v3.x.y` tags in this repo, as well as The `/v4` version will be backwards compatible with existing `v3.x.y` tags in this repo, as well as
`github.com/dgrijalva/jwt-go`. For most users this should be a drop-in replacement, if you're having `github.com/dgrijalva/jwt-go`. For most users this should be a drop-in replacement, if you're having
troubles migrating, please open an issue. troubles migrating, please open an issue.
You can replace all occurrences of `github.com/dgrijalva/jwt-go` or `github.com/golang-jwt/jwt` with `github.com/golang-jwt/jwt/v4`, either manually or by using tools such as `sed` or `gofmt`. You can replace all occurrences of `github.com/dgrijalva/jwt-go` or `github.com/golang-jwt/jwt` with `git.internal/re/jwt/v4`, either manually or by using tools such as `sed` or `gofmt`.
And then you'd typically run: And then you'd typically run:
``` ```
go get github.com/golang-jwt/jwt/v4 go get git.internal/re/jwt/v4
go mod tidy go mod tidy
``` ```

View File

@ -1,7 +1,7 @@
# jwt-go # jwt-go
[![build](https://github.com/golang-jwt/jwt/actions/workflows/build.yml/badge.svg)](https://github.com/golang-jwt/jwt/actions/workflows/build.yml) [![build](https://github.com/golang-jwt/jwt/actions/workflows/build.yml/badge.svg)](https://github.com/golang-jwt/jwt/actions/workflows/build.yml)
[![Go Reference](https://pkg.go.dev/badge/github.com/golang-jwt/jwt/v4.svg)](https://pkg.go.dev/github.com/golang-jwt/jwt/v4) [![Go Reference](https://pkg.go.dev/badge/git.internal/re/jwt/v4.svg)](https://pkg.go.dev/git.internal/re/jwt/v4)
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](https://datatracker.ietf.org/doc/html/rfc7519). A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](https://datatracker.ietf.org/doc/html/rfc7519).
@ -36,19 +36,41 @@ The part in the middle is the interesting bit. It's called the Claims and conta
This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own. This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own.
## Installation Guidelines
1. To install the jwt package, you first need to have [Go](https://go.dev/doc/install) installed, then you can use the command below to add `jwt-go` as a dependency in your Go program.
```sh
go get -u git.internal/re/jwt/v4
```
2. Import it in your code:
```go
import "git.internal/re/jwt/v4"
```
## Examples ## Examples
See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt) for examples of usage: See [the project documentation](https://pkg.go.dev/git.internal/re/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 parsing and validating a token](https://pkg.go.dev/git.internal/re/jwt/v4#example-Parse-Hmac)
* [Simple example of building and signing a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-New-Hmac) * [Simple example of building and signing a token](https://pkg.go.dev/git.internal/re/jwt/v4#example-New-Hmac)
* [Directory of Examples](https://pkg.go.dev/github.com/golang-jwt/jwt#pkg-examples) * [Directory of Examples](https://pkg.go.dev/git.internal/re/jwt/v4#pkg-examples)
## Extensions ## Extensions
This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`. This library publishes all the necessary components for adding your own signing methods or key functions. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod` or provide a `jwt.Keyfunc`.
Here's an example of an extension that integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS): https://github.com/someone1/gcp-jwt-go A common use case would be integrating with different 3rd party signature providers, like key management services from various cloud providers or Hardware Security Modules (HSMs) or to implement additional standards.
| Extension | Purpose | Repo |
| --------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| GCP | Integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS) | https://github.com/someone1/gcp-jwt-go |
| AWS | Integrates with AWS Key Management Service, KMS | https://github.com/matelang/jwt-go-aws-kms |
| JWKS | Provides support for JWKS ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) as a `jwt.Keyfunc` | https://github.com/MicahParks/keyfunc |
*Disclaimer*: Unless otherwise specified, these integrations are maintained by third parties and should not be considered as a primary offer by any of the mentioned cloud providers
## Compliance ## Compliance
@ -74,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 author of the token was in the possession of the signing secret
* The data has not been modified since it was signed * 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 ### Choosing a Signing Method
@ -88,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: 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 [HMAC signing method](https://pkg.go.dev/git.internal/re/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#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation * The [RSA signing method](https://pkg.go.dev/git.internal/re/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#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation * The [ECDSA signing method](https://pkg.go.dev/git.internal/re/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#SigningMethodEd25519) (`Ed25519`) expect `ed25519.PrivateKey` for signing and `ed25519.PublicKey` for validation * The [EdDSA signing method](https://pkg.go.dev/git.internal/re/jwt/v4#SigningMethodEd25519) (`Ed25519`) expect `ed25519.PrivateKey` for signing and `ed25519.PublicKey` for validation
### JWT and OAuth ### JWT and OAuth
@ -109,6 +131,8 @@ This library uses descriptive error messages whenever possible. If you are not g
## More ## 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/git.internal/re/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. 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.
[golang-jwt](https://github.com/orgs/golang-jwt) incorporates a modified version of the JWT logo, which is distributed under the terms of the [MIT License](https://github.com/jsonwebtoken/jsonwebtoken.github.io/blob/master/LICENSE.txt).

19
SECURITY.md Normal file
View File

@ -0,0 +1,19 @@
# Security Policy
## Supported Versions
As of February 2022 (and until this document is updated), the latest version `v4` is supported.
## Reporting a Vulnerability
If you think you found a vulnerability, and even if you are not sure, please report it to jwt-go-security@googlegroups.com or one of the other [golang-jwt maintainers](https://github.com/orgs/golang-jwt/people). Please try be explicit, describe steps to reproduce the security issue with code example(s).
You will receive a response within a timely manner. If the issue is confirmed, we will do our best to release a patch as soon as possible given the complexity of the problem.
## Public Discussions
Please avoid publicly discussing a potential security vulnerability.
Let's take this offline and find a solution first, this limits the potential impact as much as possible.
We appreciate your help!

View File

@ -56,17 +56,17 @@ func (c RegisteredClaims) Valid() error {
// default value in Go, let's not fail the verification for them. // default value in Go, let's not fail the verification for them.
if !c.VerifyExpiresAt(now, false) { if !c.VerifyExpiresAt(now, false) {
delta := now.Sub(c.ExpiresAt.Time) delta := now.Sub(c.ExpiresAt.Time)
vErr.Inner = fmt.Errorf("token is expired by %v", delta) vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
vErr.Errors |= ValidationErrorExpired vErr.Errors |= ValidationErrorExpired
} }
if !c.VerifyIssuedAt(now, false) { if !c.VerifyIssuedAt(now, false) {
vErr.Inner = fmt.Errorf("token used before issued") vErr.Inner = ErrTokenUsedBeforeIssued
vErr.Errors |= ValidationErrorIssuedAt vErr.Errors |= ValidationErrorIssuedAt
} }
if !c.VerifyNotBefore(now, false) { if !c.VerifyNotBefore(now, false) {
vErr.Inner = fmt.Errorf("token is not valid yet") vErr.Inner = ErrTokenNotValidYet
vErr.Errors |= ValidationErrorNotValidYet vErr.Errors |= ValidationErrorNotValidYet
} }
@ -113,6 +113,12 @@ func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool) bool {
return verifyNbf(&c.NotBefore.Time, cmp, req) return verifyNbf(&c.NotBefore.Time, cmp, req)
} }
// VerifyIssuer compares the iss claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *RegisteredClaims) VerifyIssuer(cmp string, req bool) bool {
return verifyIss(c.Issuer, cmp, req)
}
// StandardClaims are a structured version of the JWT Claims Set, as referenced at // StandardClaims are a structured version of the JWT Claims Set, as referenced at
// https://datatracker.ietf.org/doc/html/rfc7519#section-4. They do not follow the // https://datatracker.ietf.org/doc/html/rfc7519#section-4. They do not follow the
// specification exactly, since they were based on an earlier draft of the // specification exactly, since they were based on an earlier draft of the
@ -143,17 +149,17 @@ func (c StandardClaims) Valid() error {
// default value in Go, let's not fail the verification for them. // default value in Go, let's not fail the verification for them.
if !c.VerifyExpiresAt(now, false) { if !c.VerifyExpiresAt(now, false) {
delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
vErr.Inner = fmt.Errorf("token is expired by %v", delta) vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
vErr.Errors |= ValidationErrorExpired vErr.Errors |= ValidationErrorExpired
} }
if !c.VerifyIssuedAt(now, false) { if !c.VerifyIssuedAt(now, false) {
vErr.Inner = fmt.Errorf("token used before issued") vErr.Inner = ErrTokenUsedBeforeIssued
vErr.Errors |= ValidationErrorIssuedAt vErr.Errors |= ValidationErrorIssuedAt
} }
if !c.VerifyNotBefore(now, false) { if !c.VerifyNotBefore(now, false) {
vErr.Inner = fmt.Errorf("token is not valid yet") vErr.Inner = ErrTokenNotValidYet
vErr.Errors |= ValidationErrorNotValidYet vErr.Errors |= ValidationErrorNotValidYet
} }
@ -259,9 +265,5 @@ func verifyIss(iss string, cmp string, required bool) bool {
if iss == "" { if iss == "" {
return !required return !required
} }
if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 { return subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0
return true
} else {
return false
}
} }

View File

@ -16,4 +16,4 @@ To simply display a token, use:
You can install this tool with the following command: You can install this tool with the following command:
go install github.com/golang-jwt/jwt/v4/cmd/jwt go install git.internal/re/jwt/v4/cmd/jwt

View File

@ -3,6 +3,7 @@
// //
// Example usage: // Example usage:
// The following will create and sign a token, then verify it and output the original claims. // 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 package main
@ -11,13 +12,12 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
) )
var ( var (
@ -91,7 +91,7 @@ func loadData(p string) ([]byte, error) {
return nil, err return nil, err
} }
} }
return ioutil.ReadAll(rdr) return io.ReadAll(rdr)
} }
// Print a json object in accordance with the prophecy (or the command line options) // Print a json object in accordance with the prophecy (or the command line options)

View File

@ -2,11 +2,11 @@ package jwt_test
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"io/ioutil" "os"
"strings" "strings"
"testing" "testing"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
) )
var ecdsaTestData = []struct { var ecdsaTestData = []struct {
@ -55,7 +55,7 @@ func TestECDSAVerify(t *testing.T) {
for _, data := range ecdsaTestData { for _, data := range ecdsaTestData {
var err error var err error
key, _ := ioutil.ReadFile(data.keys["public"]) key, _ := os.ReadFile(data.keys["public"])
var ecdsaKey *ecdsa.PublicKey var ecdsaKey *ecdsa.PublicKey
if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil { if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil {
@ -78,7 +78,7 @@ func TestECDSAVerify(t *testing.T) {
func TestECDSASign(t *testing.T) { func TestECDSASign(t *testing.T) {
for _, data := range ecdsaTestData { for _, data := range ecdsaTestData {
var err error var err error
key, _ := ioutil.ReadFile(data.keys["private"]) key, _ := os.ReadFile(data.keys["private"])
var ecdsaKey *ecdsa.PrivateKey var ecdsaKey *ecdsa.PrivateKey
if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil { if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil {
@ -90,7 +90,6 @@ func TestECDSASign(t *testing.T) {
toSign := strings.Join(parts[0:2], ".") toSign := strings.Join(parts[0:2], ".")
method := jwt.GetSigningMethod(data.alg) method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(toSign, ecdsaKey) sig, err := method.Sign(toSign, ecdsaKey)
if err != nil { if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err) t.Errorf("[%v] Error signing token: %v", data.name, err)
} }
@ -108,7 +107,7 @@ func TestECDSASign(t *testing.T) {
func BenchmarkECDSAParsing(b *testing.B) { func BenchmarkECDSAParsing(b *testing.B) {
for _, data := range ecdsaTestData { for _, data := range ecdsaTestData {
key, _ := ioutil.ReadFile(data.keys["private"]) key, _ := os.ReadFile(data.keys["private"])
b.Run(data.name, func(b *testing.B) { b.Run(data.name, func(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
@ -126,7 +125,7 @@ func BenchmarkECDSAParsing(b *testing.B) {
func BenchmarkECDSASigning(b *testing.B) { func BenchmarkECDSASigning(b *testing.B) {
for _, data := range ecdsaTestData { for _, data := range ecdsaTestData {
key, _ := ioutil.ReadFile(data.keys["private"]) key, _ := os.ReadFile(data.keys["private"])
ecdsaKey, err := jwt.ParseECPrivateKeyFromPEM(key) ecdsaKey, err := jwt.ParseECPrivateKeyFromPEM(key)
if err != nil { if err != nil {

View File

@ -1,11 +1,11 @@
package jwt_test package jwt_test
import ( import (
"io/ioutil" "os"
"strings" "strings"
"testing" "testing"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
) )
var ed25519TestData = []struct { var ed25519TestData = []struct {
@ -38,7 +38,7 @@ func TestEd25519Verify(t *testing.T) {
for _, data := range ed25519TestData { for _, data := range ed25519TestData {
var err error var err error
key, _ := ioutil.ReadFile(data.keys["public"]) key, _ := os.ReadFile(data.keys["public"])
ed25519Key, err := jwt.ParseEdPublicKeyFromPEM(key) ed25519Key, err := jwt.ParseEdPublicKeyFromPEM(key)
if err != nil { if err != nil {
@ -62,7 +62,7 @@ func TestEd25519Verify(t *testing.T) {
func TestEd25519Sign(t *testing.T) { func TestEd25519Sign(t *testing.T) {
for _, data := range ed25519TestData { for _, data := range ed25519TestData {
var err error var err error
key, _ := ioutil.ReadFile(data.keys["private"]) key, _ := os.ReadFile(data.keys["private"])
ed25519Key, err := jwt.ParseEdPrivateKeyFromPEM(key) ed25519Key, err := jwt.ParseEdPrivateKeyFromPEM(key)
if err != nil { if err != nil {

View File

@ -9,6 +9,18 @@ var (
ErrInvalidKey = errors.New("key is invalid") ErrInvalidKey = errors.New("key is invalid")
ErrInvalidKeyType = errors.New("key is of invalid type") ErrInvalidKeyType = errors.New("key is of invalid type")
ErrHashUnavailable = errors.New("the requested hash function is unavailable") ErrHashUnavailable = errors.New("the requested hash function is unavailable")
ErrTokenMalformed = errors.New("token is malformed")
ErrTokenUnverifiable = errors.New("token is unverifiable")
ErrTokenSignatureInvalid = errors.New("token signature is invalid")
ErrTokenInvalidAudience = errors.New("token has invalid audience")
ErrTokenExpired = errors.New("token is expired")
ErrTokenUsedBeforeIssued = errors.New("token used before issued")
ErrTokenInvalidIssuer = errors.New("token has invalid issuer")
ErrTokenNotValidYet = errors.New("token is not valid yet")
ErrTokenInvalidId = errors.New("token has invalid id")
ErrTokenInvalidClaims = errors.New("token has invalid claims")
) )
// The errors that might occur when parsing and validating a token // The errors that might occur when parsing and validating a token
@ -62,3 +74,39 @@ func (e *ValidationError) Unwrap() error {
func (e *ValidationError) valid() bool { func (e *ValidationError) valid() bool {
return e.Errors == 0 return e.Errors == 0
} }
// Is checks if this ValidationError is of the supplied error. We are first checking for the exact error message
// by comparing the inner error message. If that fails, we compare using the error flags. This way we can use
// custom error messages (mainly for backwards compatability) and still leverage errors.Is using the global error variables.
func (e *ValidationError) Is(err error) bool {
// Check, if our inner error is a direct match
if errors.Is(errors.Unwrap(e), err) {
return true
}
// Otherwise, we need to match using our error flags
switch err {
case ErrTokenMalformed:
return e.Errors&ValidationErrorMalformed != 0
case ErrTokenUnverifiable:
return e.Errors&ValidationErrorUnverifiable != 0
case ErrTokenSignatureInvalid:
return e.Errors&ValidationErrorSignatureInvalid != 0
case ErrTokenInvalidAudience:
return e.Errors&ValidationErrorAudience != 0
case ErrTokenExpired:
return e.Errors&ValidationErrorExpired != 0
case ErrTokenUsedBeforeIssued:
return e.Errors&ValidationErrorIssuedAt != 0
case ErrTokenInvalidIssuer:
return e.Errors&ValidationErrorIssuer != 0
case ErrTokenNotValidYet:
return e.Errors&ValidationErrorNotValidYet != 0
case ErrTokenInvalidId:
return e.Errors&ValidationErrorId != 0
case ErrTokenInvalidClaims:
return e.Errors&ValidationErrorClaimsInvalid != 0
}
return false
}

View File

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
) )
// Example (atypical) using the RegisteredClaims type by itself to parse a token. // Example (atypical) using the RegisteredClaims type by itself to parse a token.
@ -25,7 +25,7 @@ func ExampleNewWithClaims_registeredClaims() {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey) ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err) fmt.Printf("%v %v", ss, err)
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.0XN_1Tpp9FszFOonIBpwha0c_SfnNI22DhTnjMshPg8 <nil> // Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.0XN_1Tpp9FszFOonIBpwha0c_SfnNI22DhTnjMshPg8 <nil>
} }
// Example creating a token using a custom claims type. The RegisteredClaims is embedded // Example creating a token using a custom claims type. The RegisteredClaims is embedded
@ -67,7 +67,7 @@ func ExampleNewWithClaims_customClaimsType() {
ss, err := token.SignedString(mySigningKey) ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err) fmt.Printf("%v %v", ss, err)
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.xVuY2FZ_MRXMIEgVQ7J-TFtaucVFRXUzHm9LmV41goM <nil> // Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.xVuY2FZ_MRXMIEgVQ7J-TFtaucVFRXUzHm9LmV41goM <nil>
} }
// Example creating a token using a custom claims type. The StandardClaim is embedded // Example creating a token using a custom claims type. The StandardClaim is embedded
@ -95,33 +95,23 @@ func ExampleParseWithClaims_customClaimsType() {
// An example of parsing the error types using bitfield checks // An example of parsing the error types using bitfield checks
func ExampleParse_errorChecking() { func ExampleParse_errorChecking() {
var (
token *jwt.Token
ve *jwt.ValidationError
err error
)
// Token from another example. This token is expired // Token from another example. This token is expired
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c" tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
token, err = jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil return []byte("AllYourBase"), nil
}) })
if token.Valid { if token.Valid {
fmt.Println("You look nice today") fmt.Println("You look nice today")
} else if errors.As(err, &ve) { } else if errors.Is(err, jwt.ErrTokenMalformed) {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
fmt.Println("That's not even a token") fmt.Println("That's not even a token")
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { } else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
// Token is either expired or not active yet // Token is either expired or not active yet
fmt.Println("Timing is everything") fmt.Println("Timing is everything")
} else { } else {
fmt.Println("Couldn't handle this token:", err) fmt.Println("Couldn't handle this token:", err)
} }
} else {
fmt.Println("Couldn't handle this token:", err)
}
// Output: Timing is everything // Output: Timing is everything
} }

8
go.mod
View File

@ -1,3 +1,7 @@
module github.com/golang-jwt/jwt/v4 module git.internal/re/jwt/v4
go 1.15 go 1.16
retract (
v4.4.0 // Contains a backwards incompatible change to the Claims interface.
)

View File

@ -2,10 +2,10 @@ package jwt_test
import ( import (
"fmt" "fmt"
"io/ioutil" "os"
"time" "time"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
) )
// For HMAC signing method, the key can be any []byte. It is recommended to generate // For HMAC signing method, the key can be any []byte. It is recommended to generate
@ -15,7 +15,7 @@ var hmacSampleSecret []byte
func init() { func init() {
// Load sample key data // Load sample key data
if keyData, e := ioutil.ReadFile("test/hmacTestKey"); e == nil { if keyData, e := os.ReadFile("test/hmacTestKey"); e == nil {
hmacSampleSecret = keyData hmacSampleSecret = keyData
} else { } else {
panic(e) panic(e)

View File

@ -1,11 +1,11 @@
package jwt_test package jwt_test
import ( import (
"io/ioutil" "os"
"strings" "strings"
"testing" "testing"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
) )
var hmacTestData = []struct { var hmacTestData = []struct {
@ -46,7 +46,7 @@ var hmacTestData = []struct {
} }
// Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1 // Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1
var hmacTestKey, _ = ioutil.ReadFile("test/hmacTestKey") var hmacTestKey, _ = os.ReadFile("test/hmacTestKey")
func TestHMACVerify(t *testing.T) { func TestHMACVerify(t *testing.T) {
for _, data := range hmacTestData { for _, data := range hmacTestData {

View File

@ -8,16 +8,16 @@ import (
"crypto/rsa" "crypto/rsa"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strings" "strings"
"time" "time"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
"github.com/golang-jwt/jwt/v4/request" "git.internal/re/jwt/v4/request"
) )
// location of the files used for signing and verification // location of the files used for signing and verification
@ -34,13 +34,13 @@ var (
// read the key files before starting http handlers // read the key files before starting http handlers
func init() { func init() {
signBytes, err := ioutil.ReadFile(privKeyPath) signBytes, err := os.ReadFile(privKeyPath)
fatal(err) fatal(err)
signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes) signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
fatal(err) fatal(err)
verifyBytes, err := ioutil.ReadFile(pubKeyPath) verifyBytes, err := os.ReadFile(pubKeyPath)
fatal(err) fatal(err)
verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes) verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
@ -73,7 +73,7 @@ type CustomerInfo struct {
} }
type CustomClaimsExample struct { type CustomClaimsExample struct {
*jwt.RegisteredClaims jwt.RegisteredClaims
TokenType string TokenType string
CustomerInfo CustomerInfo
} }
@ -109,11 +109,10 @@ func Example_getTokenViaHTTP() {
claims := token.Claims.(*CustomClaimsExample) claims := token.Claims.(*CustomClaimsExample)
fmt.Println(claims.CustomerInfo.Name) fmt.Println(claims.CustomerInfo.Name)
//Output: test // Output: test
} }
func Example_useTokenViaHTTP() { func Example_useTokenViaHTTP() {
// Make a sample token // Make a sample token
// In a real world situation, this token will have been acquired from // In a real world situation, this token will have been acquired from
// some other API call (see Example_getTokenViaHTTP) // some other API call (see Example_getTokenViaHTTP)
@ -142,7 +141,7 @@ func createToken(user string) (string, error) {
// set our claims // set our claims
t.Claims = &CustomClaimsExample{ t.Claims = &CustomClaimsExample{
&jwt.RegisteredClaims{ jwt.RegisteredClaims{
// set the expire time // set the expire time
// see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4 // see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 1)), ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 1)),
@ -197,7 +196,6 @@ func restrictedHandler(w http.ResponseWriter, r *http.Request) {
// we also only use its public counter part to verify // we also only use its public counter part to verify
return verifyKey, nil return verifyKey, nil
}, request.WithClaims(&CustomClaimsExample{})) }, request.WithClaims(&CustomClaimsExample{}))
// If the token is missing or invalid, return error // If the token is missing or invalid, return error
if err != nil { if err != nil {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)

View File

@ -126,16 +126,19 @@ func (m MapClaims) Valid() error {
now := TimeFunc().Unix() now := TimeFunc().Unix()
if !m.VerifyExpiresAt(now, false) { if !m.VerifyExpiresAt(now, false) {
// TODO(oxisto): this should be replaced with ErrTokenExpired
vErr.Inner = errors.New("Token is expired") vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired vErr.Errors |= ValidationErrorExpired
} }
if !m.VerifyIssuedAt(now, false) { if !m.VerifyIssuedAt(now, false) {
// TODO(oxisto): this should be replaced with ErrTokenUsedBeforeIssued
vErr.Inner = errors.New("Token used before issued") vErr.Inner = errors.New("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt vErr.Errors |= ValidationErrorIssuedAt
} }
if !m.VerifyNotBefore(now, false) { if !m.VerifyNotBefore(now, false) {
// TODO(oxisto): this should be replaced with ErrTokenNotValidYet
vErr.Inner = errors.New("Token is not valid yet") vErr.Inner = errors.New("Token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet vErr.Errors |= ValidationErrorNotValidYet
} }

View File

@ -110,13 +110,13 @@ func TestMapClaimsVerifyExpiresAtExpire(t *testing.T) {
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got) t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got)
} }
got = mapClaims.VerifyExpiresAt(exp + 1, true) got = mapClaims.VerifyExpiresAt(exp+1, true)
if want != got { if want != got {
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got) t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got)
} }
want = true want = true
got = mapClaims.VerifyExpiresAt(exp - 1, true) got = mapClaims.VerifyExpiresAt(exp-1, true)
if want != got { if want != got {
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got) t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got)
} }

View File

@ -4,7 +4,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
) )
var noneTestData = []struct { var noneTestData = []struct {

View File

@ -42,6 +42,13 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc) 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) { func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
token, parts, err := p.ParseUnverified(tokenString, claims) token, parts, err := p.ParseUnverified(tokenString, claims)
if err != nil { if err != nil {

View File

@ -1,6 +1,6 @@
package jwt package jwt
// ParserOption is used to implement functional-style options that modify the behaviour of the parser. To add // ParserOption is used to implement functional-style options that modify the behavior of the parser. To add
// new options, just create a function (ideally beginning with With or Without) that returns an anonymous function that // new options, just create a function (ideally beginning with With or Without) that returns an anonymous function that
// takes a *Parser type as input and manipulates its configuration accordingly. // takes a *Parser type as input and manipulates its configuration accordingly.
type ParserOption func(*Parser) type ParserOption func(*Parser)
@ -13,7 +13,7 @@ func WithValidMethods(methods []string) ParserOption {
} }
} }
// WithJSONNumber is an option to configure the underyling JSON parser with UseNumber // WithJSONNumber is an option to configure the underlying JSON parser with UseNumber
func WithJSONNumber() ParserOption { func WithJSONNumber() ParserOption {
return func(p *Parser) { return func(p *Parser) {
p.UseJSONNumber = true p.UseJSONNumber = true

View File

@ -10,8 +10,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
"github.com/golang-jwt/jwt/v4/test" "git.internal/re/jwt/v4/test"
) )
var errKeyFuncError error = fmt.Errorf("error loading key") var errKeyFuncError error = fmt.Errorf("error loading key")
@ -42,7 +42,6 @@ func init() {
// Load private keys // Load private keys
jwtTestRSAPrivateKey = test.LoadRSAPrivateKeyFromDisk("test/sample_key") jwtTestRSAPrivateKey = test.LoadRSAPrivateKeyFromDisk("test/sample_key")
jwtTestEC256PrivateKey = test.LoadECPrivateKeyFromDisk("test/ec256-private.pem") jwtTestEC256PrivateKey = test.LoadECPrivateKeyFromDisk("test/ec256-private.pem")
} }
var jwtTestData = []struct { var jwtTestData = []struct {
@ -52,6 +51,7 @@ var jwtTestData = []struct {
claims jwt.Claims claims jwt.Claims
valid bool valid bool
errors uint32 errors uint32
err []error
parser *jwt.Parser parser *jwt.Parser
signingMethod jwt.SigningMethod // The method to sign the JWT token for test purpose signingMethod jwt.SigningMethod // The method to sign the JWT token for test purpose
}{ }{
@ -63,6 +63,7 @@ var jwtTestData = []struct {
true, true,
0, 0,
nil, nil,
nil,
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
{ {
@ -72,6 +73,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar", "exp": float64(time.Now().Unix() - 100)}, jwt.MapClaims{"foo": "bar", "exp": float64(time.Now().Unix() - 100)},
false, false,
jwt.ValidationErrorExpired, jwt.ValidationErrorExpired,
[]error{jwt.ErrTokenExpired},
nil, nil,
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -82,6 +84,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)}, jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)},
false, false,
jwt.ValidationErrorNotValidYet, jwt.ValidationErrorNotValidYet,
[]error{jwt.ErrTokenNotValidYet},
nil, nil,
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -92,6 +95,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)}, jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)},
false, false,
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
[]error{jwt.ErrTokenNotValidYet},
nil, nil,
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -102,6 +106,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar"}, jwt.MapClaims{"foo": "bar"},
false, false,
jwt.ValidationErrorSignatureInvalid, jwt.ValidationErrorSignatureInvalid,
[]error{jwt.ErrTokenSignatureInvalid, rsa.ErrVerification},
nil, nil,
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -112,6 +117,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar"}, jwt.MapClaims{"foo": "bar"},
false, false,
jwt.ValidationErrorUnverifiable, jwt.ValidationErrorUnverifiable,
[]error{jwt.ErrTokenUnverifiable},
nil, nil,
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -122,6 +128,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar"}, jwt.MapClaims{"foo": "bar"},
false, false,
jwt.ValidationErrorSignatureInvalid, jwt.ValidationErrorSignatureInvalid,
[]error{jwt.ErrTokenSignatureInvalid},
nil, nil,
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -132,6 +139,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar"}, jwt.MapClaims{"foo": "bar"},
false, false,
jwt.ValidationErrorUnverifiable, jwt.ValidationErrorUnverifiable,
[]error{jwt.ErrTokenUnverifiable, errKeyFuncError},
nil, nil,
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -142,6 +150,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar"}, jwt.MapClaims{"foo": "bar"},
false, false,
jwt.ValidationErrorSignatureInvalid, jwt.ValidationErrorSignatureInvalid,
[]error{jwt.ErrTokenSignatureInvalid},
&jwt.Parser{ValidMethods: []string{"HS256"}}, &jwt.Parser{ValidMethods: []string{"HS256"}},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -152,6 +161,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar"}, jwt.MapClaims{"foo": "bar"},
true, true,
0, 0,
nil,
&jwt.Parser{ValidMethods: []string{"RS256", "HS256"}}, &jwt.Parser{ValidMethods: []string{"RS256", "HS256"}},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -162,6 +172,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar"}, jwt.MapClaims{"foo": "bar"},
false, false,
jwt.ValidationErrorSignatureInvalid, jwt.ValidationErrorSignatureInvalid,
[]error{jwt.ErrTokenSignatureInvalid},
&jwt.Parser{ValidMethods: []string{"RS256", "HS256"}}, &jwt.Parser{ValidMethods: []string{"RS256", "HS256"}},
jwt.SigningMethodES256, jwt.SigningMethodES256,
}, },
@ -172,6 +183,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar"}, jwt.MapClaims{"foo": "bar"},
true, true,
0, 0,
nil,
&jwt.Parser{ValidMethods: []string{"HS256", "ES256"}}, &jwt.Parser{ValidMethods: []string{"HS256", "ES256"}},
jwt.SigningMethodES256, jwt.SigningMethodES256,
}, },
@ -182,6 +194,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": json.Number("123.4")}, jwt.MapClaims{"foo": json.Number("123.4")},
true, true,
0, 0,
nil,
&jwt.Parser{UseJSONNumber: true}, &jwt.Parser{UseJSONNumber: true},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -194,6 +207,7 @@ var jwtTestData = []struct {
}, },
true, true,
0, 0,
nil,
&jwt.Parser{UseJSONNumber: true}, &jwt.Parser{UseJSONNumber: true},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -204,6 +218,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar", "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))}, jwt.MapClaims{"foo": "bar", "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
false, false,
jwt.ValidationErrorExpired, jwt.ValidationErrorExpired,
[]error{jwt.ErrTokenExpired},
&jwt.Parser{UseJSONNumber: true}, &jwt.Parser{UseJSONNumber: true},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -214,6 +229,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))}, jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
false, false,
jwt.ValidationErrorNotValidYet, jwt.ValidationErrorNotValidYet,
[]error{jwt.ErrTokenNotValidYet},
&jwt.Parser{UseJSONNumber: true}, &jwt.Parser{UseJSONNumber: true},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -224,6 +240,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100)), "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))}, jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100)), "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
false, false,
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
[]error{jwt.ErrTokenNotValidYet},
&jwt.Parser{UseJSONNumber: true}, &jwt.Parser{UseJSONNumber: true},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -234,6 +251,7 @@ var jwtTestData = []struct {
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))}, jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
true, true,
0, 0,
nil,
&jwt.Parser{UseJSONNumber: true, SkipClaimsValidation: true}, &jwt.Parser{UseJSONNumber: true, SkipClaimsValidation: true},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -246,6 +264,7 @@ var jwtTestData = []struct {
}, },
true, true,
0, 0,
nil,
&jwt.Parser{UseJSONNumber: true}, &jwt.Parser{UseJSONNumber: true},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -258,6 +277,7 @@ var jwtTestData = []struct {
}, },
true, true,
0, 0,
nil,
&jwt.Parser{UseJSONNumber: true}, &jwt.Parser{UseJSONNumber: true},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -270,6 +290,7 @@ var jwtTestData = []struct {
}, },
true, true,
0, 0,
nil,
&jwt.Parser{UseJSONNumber: true}, &jwt.Parser{UseJSONNumber: true},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -282,6 +303,7 @@ var jwtTestData = []struct {
}, },
false, false,
jwt.ValidationErrorMalformed, jwt.ValidationErrorMalformed,
[]error{jwt.ErrTokenMalformed},
&jwt.Parser{UseJSONNumber: true}, &jwt.Parser{UseJSONNumber: true},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -294,6 +316,7 @@ var jwtTestData = []struct {
}, },
false, false,
jwt.ValidationErrorMalformed, jwt.ValidationErrorMalformed,
[]error{jwt.ErrTokenMalformed},
&jwt.Parser{UseJSONNumber: true}, &jwt.Parser{UseJSONNumber: true},
jwt.SigningMethodRS256, jwt.SigningMethodRS256,
}, },
@ -314,11 +337,9 @@ func signToken(claims jwt.Claims, signingMethod jwt.SigningMethod) string {
} }
func TestParser_Parse(t *testing.T) { func TestParser_Parse(t *testing.T) {
// Iterate over test data set and run tests // Iterate over test data set and run tests
for _, data := range jwtTestData { for _, data := range jwtTestData {
t.Run(data.name, func(t *testing.T) { t.Run(data.name, func(t *testing.T) {
// If the token string is blank, use helper function to generate string // If the token string is blank, use helper function to generate string
if data.tokenString == "" { if data.tokenString == "" {
data.tokenString = signToken(data.claims, data.signingMethod) data.tokenString = signToken(data.claims, data.signingMethod)
@ -328,7 +349,7 @@ func TestParser_Parse(t *testing.T) {
var token *jwt.Token var token *jwt.Token
var ve *jwt.ValidationError var ve *jwt.ValidationError
var err error var err error
var parser = data.parser parser := data.parser
if parser == nil { if parser == nil {
parser = new(jwt.Parser) parser = new(jwt.Parser)
} }
@ -375,6 +396,22 @@ func TestParser_Parse(t *testing.T) {
} }
} }
} }
if data.err != nil {
if err == nil {
t.Errorf("[%v] Expecting error(s). Didn't get one.", data.name)
} else {
all := false
for _, e := range data.err {
all = errors.Is(err, e)
}
if !all {
t.Errorf("[%v] Errors don't match expectation. %v should contain all of %v", data.name, err, data.err)
}
}
}
if data.valid { if data.valid {
if token.Signature == "" { if token.Signature == "" {
t.Errorf("[%v] Signature is left unpopulated after parsing", data.name) t.Errorf("[%v] Signature is left unpopulated after parsing", data.name)
@ -389,7 +426,6 @@ func TestParser_Parse(t *testing.T) {
} }
func TestParser_ParseUnverified(t *testing.T) { func TestParser_ParseUnverified(t *testing.T) {
// Iterate over test data set and run tests // Iterate over test data set and run tests
for _, data := range jwtTestData { for _, data := range jwtTestData {
// Skip test data, that intentionally contains malformed tokens, as they would lead to an error // Skip test data, that intentionally contains malformed tokens, as they would lead to an error
@ -406,7 +442,7 @@ func TestParser_ParseUnverified(t *testing.T) {
// Parse the token // Parse the token
var token *jwt.Token var token *jwt.Token
var err error var err error
var parser = data.parser parser := data.parser
if parser == nil { if parser == nil {
parser = new(jwt.Parser) parser = new(jwt.Parser)
} }
@ -449,6 +485,7 @@ var setPaddingTestData = []struct {
tokenString string tokenString string
claims jwt.Claims claims jwt.Claims
paddedDecode bool paddedDecode bool
strictDecode bool
signingMethod jwt.SigningMethod signingMethod jwt.SigningMethod
keyfunc jwt.Keyfunc keyfunc jwt.Keyfunc
valid bool valid bool
@ -507,19 +544,108 @@ var setPaddingTestData = []struct {
keyfunc: paddedKeyFunc, keyfunc: paddedKeyFunc,
valid: true, valid: true,
}, },
// DecodeStrict tests, DecodePaddingAllowed=false
{
name: "Validated non-padded token with padding disabled, non-strict decode, non-tweaked signature",
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
"g",
claims: nil,
paddedDecode: false,
strictDecode: false,
signingMethod: jwt.SigningMethodRS256,
keyfunc: defaultKeyFunc,
valid: true,
},
{
name: "Validated non-padded token with padding disabled, non-strict decode, tweaked signature",
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
"h",
claims: nil,
paddedDecode: false,
strictDecode: false,
signingMethod: jwt.SigningMethodRS256,
keyfunc: defaultKeyFunc,
valid: true,
},
{
name: "Validated non-padded token with padding disabled, strict decode, non-tweaked signature",
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
"g",
claims: nil,
paddedDecode: false,
strictDecode: true,
signingMethod: jwt.SigningMethodRS256,
keyfunc: defaultKeyFunc,
valid: true,
},
{
name: "Error for non-padded token with padding disabled, strict decode, tweaked signature",
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
"h",
claims: nil,
paddedDecode: false,
strictDecode: true,
signingMethod: jwt.SigningMethodRS256,
keyfunc: defaultKeyFunc,
valid: false,
},
// DecodeStrict tests, DecodePaddingAllowed=true
{
name: "Validated padded token with padding enabled, non-strict decode, non-tweaked signature",
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
"w==",
claims: nil,
paddedDecode: true,
strictDecode: false,
signingMethod: jwt.SigningMethodES256,
keyfunc: paddedKeyFunc,
valid: true,
},
{
name: "Validated padded token with padding enabled, non-strict decode, tweaked signature",
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
"x==",
claims: nil,
paddedDecode: true,
strictDecode: false,
signingMethod: jwt.SigningMethodES256,
keyfunc: paddedKeyFunc,
valid: true,
},
{
name: "Validated padded token with padding enabled, strict decode, non-tweaked signature",
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
"w==",
claims: nil,
paddedDecode: true,
strictDecode: true,
signingMethod: jwt.SigningMethodES256,
keyfunc: paddedKeyFunc,
valid: true,
},
{
name: "Error for padded token with padding enabled, strict decode, tweaked signature",
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
"x==",
claims: nil,
paddedDecode: true,
strictDecode: true,
signingMethod: jwt.SigningMethodES256,
keyfunc: paddedKeyFunc,
valid: false,
},
} }
// Extension of Parsing, this is to test out functionality specific to switching codecs with padding. // Extension of Parsing, this is to test out functionality specific to switching codecs with padding.
func TestSetPadding(t *testing.T) { func TestSetPadding(t *testing.T) {
for _, data := range setPaddingTestData { for _, data := range setPaddingTestData {
t.Run(data.name, func(t *testing.T) { t.Run(data.name, func(t *testing.T) {
jwt.DecodePaddingAllowed = data.paddedDecode
jwt.DecodeStrict = data.strictDecode
// If the token string is blank, use helper function to generate string // If the token string is blank, use helper function to generate string
jwt.DecodePaddingAllowed = data.paddedDecode
if data.tokenString == "" { if data.tokenString == "" {
data.tokenString = signToken(data.claims, data.signingMethod) data.tokenString = signToken(data.claims, data.signingMethod)
} }
// Parse the token // Parse the token
@ -538,15 +664,13 @@ func TestSetPadding(t *testing.T) {
err, err,
) )
} }
}) })
jwt.DecodePaddingAllowed = false jwt.DecodePaddingAllowed = false
jwt.DecodeStrict = false
} }
} }
func BenchmarkParseUnverified(b *testing.B) { func BenchmarkParseUnverified(b *testing.B) {
// Iterate over test data set and run tests // Iterate over test data set and run tests
for _, data := range jwtTestData { for _, data := range jwtTestData {
// If the token string is blank, use helper function to generate string // If the token string is blank, use helper function to generate string
@ -555,7 +679,7 @@ func BenchmarkParseUnverified(b *testing.B) {
} }
// Parse the token // Parse the token
var parser = data.parser parser := data.parser
if parser == nil { if parser == nil {
parser = new(jwt.Parser) parser = new(jwt.Parser)
} }

View File

@ -3,6 +3,7 @@ package request
import ( import (
"errors" "errors"
"net/http" "net/http"
"strings"
) )
// Errors // Errors
@ -58,7 +59,7 @@ func (e MultiExtractor) ExtractToken(req *http.Request) (string, error) {
for _, extractor := range e { for _, extractor := range e {
if tok, err := extractor.ExtractToken(req); tok != "" { if tok, err := extractor.ExtractToken(req); tok != "" {
return tok, nil return tok, nil
} else if err != ErrNoTokenInRequest { } else if !errors.Is(err, ErrNoTokenInRequest) {
return "", err return "", err
} }
} }
@ -79,3 +80,18 @@ func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) {
return "", err 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
}

View File

@ -89,3 +89,23 @@ func makeExampleRequest(method, path string, headers map[string]string, urlArgs
} }
return r 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)
}
}

View File

@ -3,7 +3,7 @@ package request
import ( import (
"net/http" "net/http"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
) )
// ParseFromRequest extracts and parses a JWT token from an HTTP request. // ParseFromRequest extracts and parses a JWT token from an HTTP request.

View File

@ -8,8 +8,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
"github.com/golang-jwt/jwt/v4/test" "git.internal/re/jwt/v4/test"
) )
var requestTestData = []struct { var requestTestData = []struct {

View File

@ -1,3 +1,4 @@
//go:build go1.4
// +build go1.4 // +build go1.4
package jwt package jwt

View File

@ -1,16 +1,17 @@
//go:build go1.4
// +build go1.4 // +build go1.4
package jwt_test package jwt_test
import ( import (
"crypto/rsa" "crypto/rsa"
"io/ioutil" "os"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
"github.com/golang-jwt/jwt/v4/test" "git.internal/re/jwt/v4/test"
) )
var rsaPSSTestData = []struct { var rsaPSSTestData = []struct {
@ -53,7 +54,7 @@ var rsaPSSTestData = []struct {
func TestRSAPSSVerify(t *testing.T) { func TestRSAPSSVerify(t *testing.T) {
var err error var err error
key, _ := ioutil.ReadFile("test/sample_key.pub") key, _ := os.ReadFile("test/sample_key.pub")
var rsaPSSKey *rsa.PublicKey var rsaPSSKey *rsa.PublicKey
if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM(key); err != nil { if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse RSA public key: %v", err) t.Errorf("Unable to parse RSA public key: %v", err)
@ -76,7 +77,7 @@ func TestRSAPSSVerify(t *testing.T) {
func TestRSAPSSSign(t *testing.T) { func TestRSAPSSSign(t *testing.T) {
var err error var err error
key, _ := ioutil.ReadFile("test/sample_key") key, _ := os.ReadFile("test/sample_key")
var rsaPSSKey *rsa.PrivateKey var rsaPSSKey *rsa.PrivateKey
if rsaPSSKey, err = jwt.ParseRSAPrivateKeyFromPEM(key); err != nil { if rsaPSSKey, err = jwt.ParseRSAPrivateKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse RSA private key: %v", err) t.Errorf("Unable to parse RSA private key: %v", err)

View File

@ -1,52 +1,47 @@
package jwt_test package jwt_test
import ( import (
"io/ioutil" "os"
"strings" "strings"
"testing" "testing"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
) )
var rsaTestData = []struct { var rsaTestData = []struct {
name string name string
tokenString string tokenString string
alg string alg string
claims map[string]interface{}
valid bool valid bool
}{ }{
{ {
"Basic RS256", "Basic RS256",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
"RS256", "RS256",
map[string]interface{}{"foo": "bar"},
true, true,
}, },
{ {
"Basic RS384", "Basic RS384",
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw", "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw",
"RS384", "RS384",
map[string]interface{}{"foo": "bar"},
true, true,
}, },
{ {
"Basic RS512", "Basic RS512",
"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ", "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ",
"RS512", "RS512",
map[string]interface{}{"foo": "bar"},
true, true,
}, },
{ {
"basic invalid: foo => bar", "basic invalid: foo => bar",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
"RS256", "RS256",
map[string]interface{}{"foo": "bar"},
false, false,
}, },
} }
func TestRSAVerify(t *testing.T) { func TestRSAVerify(t *testing.T) {
keyData, _ := ioutil.ReadFile("test/sample_key.pub") keyData, _ := os.ReadFile("test/sample_key.pub")
key, _ := jwt.ParseRSAPublicKeyFromPEM(keyData) key, _ := jwt.ParseRSAPublicKeyFromPEM(keyData)
for _, data := range rsaTestData { for _, data := range rsaTestData {
@ -64,7 +59,7 @@ func TestRSAVerify(t *testing.T) {
} }
func TestRSASign(t *testing.T) { func TestRSASign(t *testing.T) {
keyData, _ := ioutil.ReadFile("test/sample_key") keyData, _ := os.ReadFile("test/sample_key")
key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData) key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData)
for _, data := range rsaTestData { for _, data := range rsaTestData {
@ -83,7 +78,7 @@ func TestRSASign(t *testing.T) {
} }
func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) { func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) {
key, _ := ioutil.ReadFile("test/sample_key.pub") key, _ := os.ReadFile("test/sample_key.pub")
parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key) parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -97,7 +92,7 @@ func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) {
} }
func TestRSAWithPreParsedPrivateKey(t *testing.T) { func TestRSAWithPreParsedPrivateKey(t *testing.T) {
key, _ := ioutil.ReadFile("test/sample_key") key, _ := os.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -114,9 +109,9 @@ func TestRSAWithPreParsedPrivateKey(t *testing.T) {
} }
func TestRSAKeyParsing(t *testing.T) { func TestRSAKeyParsing(t *testing.T) {
key, _ := ioutil.ReadFile("test/sample_key") key, _ := os.ReadFile("test/sample_key")
secureKey, _ := ioutil.ReadFile("test/privateSecure.pem") secureKey, _ := os.ReadFile("test/privateSecure.pem")
pubKey, _ := ioutil.ReadFile("test/sample_key.pub") pubKey, _ := os.ReadFile("test/sample_key.pub")
badKey := []byte("All your base are belong to key") badKey := []byte("All your base are belong to key")
// Test parsePrivateKey // Test parsePrivateKey
@ -152,11 +147,10 @@ func TestRSAKeyParsing(t *testing.T) {
if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil { if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil {
t.Errorf("Parsed invalid key as valid private key: %v", k) t.Errorf("Parsed invalid key as valid private key: %v", k)
} }
} }
func BenchmarkRSAParsing(b *testing.B) { func BenchmarkRSAParsing(b *testing.B) {
key, _ := ioutil.ReadFile("test/sample_key") key, _ := os.ReadFile("test/sample_key")
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
@ -170,7 +164,7 @@ func BenchmarkRSAParsing(b *testing.B) {
} }
func BenchmarkRS256Signing(b *testing.B) { func BenchmarkRS256Signing(b *testing.B) {
key, _ := ioutil.ReadFile("test/sample_key") key, _ := os.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -180,7 +174,7 @@ func BenchmarkRS256Signing(b *testing.B) {
} }
func BenchmarkRS384Signing(b *testing.B) { func BenchmarkRS384Signing(b *testing.B) {
key, _ := ioutil.ReadFile("test/sample_key") key, _ := os.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -190,7 +184,7 @@ func BenchmarkRS384Signing(b *testing.B) {
} }
func BenchmarkRS512Signing(b *testing.B) { func BenchmarkRS512Signing(b *testing.B) {
key, _ := ioutil.ReadFile("test/sample_key") key, _ := os.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)

View File

@ -3,13 +3,13 @@ package test
import ( import (
"crypto" "crypto"
"crypto/rsa" "crypto/rsa"
"io/ioutil" "os"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
) )
func LoadRSAPrivateKeyFromDisk(location string) *rsa.PrivateKey { func LoadRSAPrivateKeyFromDisk(location string) *rsa.PrivateKey {
keyData, e := ioutil.ReadFile(location) keyData, e := os.ReadFile(location)
if e != nil { if e != nil {
panic(e.Error()) panic(e.Error())
} }
@ -21,7 +21,7 @@ func LoadRSAPrivateKeyFromDisk(location string) *rsa.PrivateKey {
} }
func LoadRSAPublicKeyFromDisk(location string) *rsa.PublicKey { func LoadRSAPublicKeyFromDisk(location string) *rsa.PublicKey {
keyData, e := ioutil.ReadFile(location) keyData, e := os.ReadFile(location)
if e != nil { if e != nil {
panic(e.Error()) panic(e.Error())
} }
@ -45,7 +45,7 @@ func MakeSampleToken(c jwt.Claims, method jwt.SigningMethod, key interface{}) st
} }
func LoadECPrivateKeyFromDisk(location string) crypto.PrivateKey { func LoadECPrivateKeyFromDisk(location string) crypto.PrivateKey {
keyData, e := ioutil.ReadFile(location) keyData, e := os.ReadFile(location)
if e != nil { if e != nil {
panic(e.Error()) panic(e.Error())
} }
@ -57,7 +57,7 @@ func LoadECPrivateKeyFromDisk(location string) crypto.PrivateKey {
} }
func LoadECPublicKeyFromDisk(location string) crypto.PublicKey { func LoadECPublicKeyFromDisk(location string) crypto.PublicKey {
keyData, e := ioutil.ReadFile(location) keyData, e := os.ReadFile(location)
if e != nil { if e != nil {
panic(e.Error()) panic(e.Error())
} }

View File

@ -7,7 +7,6 @@ import (
"time" "time"
) )
// DecodePaddingAllowed will switch the codec used for decoding JWTs respectively. Note that the JWS RFC7515 // DecodePaddingAllowed will switch the codec used for decoding JWTs respectively. Note that the JWS RFC7515
// states that the tokens will utilize a Base64url encoding with no padding. Unfortunately, some implementations // states that the tokens will utilize a Base64url encoding with no padding. Unfortunately, some implementations
// of JWT are producing non-standard tokens, and thus require support for decoding. Note that this is a global // of JWT are producing non-standard tokens, and thus require support for decoding. Note that this is a global
@ -15,6 +14,12 @@ import (
// To use the non-recommended decoding, set this boolean to `true` prior to using this package. // To use the non-recommended decoding, set this boolean to `true` prior to using this package.
var DecodePaddingAllowed bool var DecodePaddingAllowed bool
// DecodeStrict will switch the codec used for decoding JWTs into strict mode.
// In this mode, the decoder requires that trailing padding bits are zero, as described in RFC 4648 section 3.5.
// Note that this is a global variable, and updating it will change the behavior on a package level, and is also NOT go-routine safe.
// To use strict decoding, set this boolean to `true` prior to using this package.
var DecodeStrict bool
// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time). // TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
// You can override it to use another time value. This is useful for testing or if your // You can override it to use another time value. This is useful for testing or if your
// server uses a different time zone than your tokens. // server uses a different time zone than your tokens.
@ -74,22 +79,19 @@ func (t *Token) SignedString(key interface{}) (string, error) {
// the SignedString. // the SignedString.
func (t *Token) SigningString() (string, error) { func (t *Token) SigningString() (string, error) {
var err error var err error
parts := make([]string, 2)
for i := range parts {
var jsonValue []byte var jsonValue []byte
if i == 0 {
if jsonValue, err = json.Marshal(t.Header); err != nil { if jsonValue, err = json.Marshal(t.Header); err != nil {
return "", err return "", err
} }
} else { header := EncodeSegment(jsonValue)
if jsonValue, err = json.Marshal(t.Claims); err != nil { if jsonValue, err = json.Marshal(t.Claims); err != nil {
return "", err return "", err
} }
} claim := EncodeSegment(jsonValue)
parts[i] = EncodeSegment(jsonValue) return strings.Join([]string{header, claim}, "."), nil
}
return strings.Join(parts, "."), nil
} }
// Parse parses, validates, verifies the signature and returns the parsed token. // Parse parses, validates, verifies the signature and returns the parsed token.
@ -103,6 +105,11 @@ func Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token
return NewParser(options...).Parse(tokenString, keyFunc) 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) { func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error) {
return NewParser(options...).ParseWithClaims(tokenString, claims, keyFunc) return NewParser(options...).ParseWithClaims(tokenString, claims, keyFunc)
} }
@ -120,12 +127,17 @@ func EncodeSegment(seg []byte) string {
// Deprecated: In a future release, we will demote this function to a non-exported function, since it // Deprecated: In a future release, we will demote this function to a non-exported function, since it
// should only be used internally // should only be used internally
func DecodeSegment(seg string) ([]byte, error) { func DecodeSegment(seg string) ([]byte, error) {
encoding := base64.RawURLEncoding
if DecodePaddingAllowed { if DecodePaddingAllowed {
if l := len(seg) % 4; l > 0 { if l := len(seg) % 4; l > 0 {
seg += strings.Repeat("=", 4-l) seg += strings.Repeat("=", 4-l)
} }
return base64.URLEncoding.DecodeString(seg) encoding = base64.URLEncoding
} }
return base64.RawURLEncoding.DecodeString(seg) if DecodeStrict {
encoding = encoding.Strict()
}
return encoding.DecodeString(seg)
} }

79
token_test.go Normal file
View File

@ -0,0 +1,79 @@
package jwt_test
import (
"testing"
"git.internal/re/jwt/v4"
)
func TestToken_SigningString(t1 *testing.T) {
type fields struct {
Raw string
Method jwt.SigningMethod
Header map[string]interface{}
Claims jwt.Claims
Signature string
Valid bool
}
tests := []struct {
name string
fields fields
want string
wantErr bool
}{
{
name: "",
fields: fields{
Raw: "",
Method: jwt.SigningMethodHS256,
Header: map[string]interface{}{
"typ": "JWT",
"alg": jwt.SigningMethodHS256.Alg(),
},
Claims: jwt.StandardClaims{},
Signature: "",
Valid: false,
},
want: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30",
wantErr: false,
},
}
for _, tt := range tests {
t1.Run(tt.name, func(t1 *testing.T) {
t := &jwt.Token{
Raw: tt.fields.Raw,
Method: tt.fields.Method,
Header: tt.fields.Header,
Claims: tt.fields.Claims,
Signature: tt.fields.Signature,
Valid: tt.fields.Valid,
}
got, err := t.SigningString()
if (err != nil) != tt.wantErr {
t1.Errorf("SigningString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t1.Errorf("SigningString() got = %v, want %v", got, tt.want)
}
})
}
}
func BenchmarkToken_SigningString(b *testing.B) {
t := &jwt.Token{
Method: jwt.SigningMethodHS256,
Header: map[string]interface{}{
"typ": "JWT",
"alg": jwt.SigningMethodHS256.Alg(),
},
Claims: jwt.StandardClaims{},
}
b.Run("BenchmarkToken_SigningString", func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
t.SigningString()
}
})
}

View File

@ -49,9 +49,27 @@ func newNumericDateFromSeconds(f float64) *NumericDate {
// MarshalJSON is an implementation of the json.RawMessage interface and serializes the UNIX epoch // MarshalJSON is an implementation of the json.RawMessage interface and serializes the UNIX epoch
// represented in NumericDate to a byte array, using the precision specified in TimePrecision. // represented in NumericDate to a byte array, using the precision specified in TimePrecision.
func (date NumericDate) MarshalJSON() (b []byte, err error) { func (date NumericDate) MarshalJSON() (b []byte, err error) {
f := float64(date.Truncate(TimePrecision).UnixNano()) / float64(time.Second) var prec int
if TimePrecision < time.Second {
prec = int(math.Log10(float64(time.Second) / float64(TimePrecision)))
}
truncatedDate := date.Truncate(TimePrecision)
return []byte(strconv.FormatFloat(f, 'f', -1, 64)), nil // For very large timestamps, UnixNano would overflow an int64, but this
// function requires nanosecond level precision, so we have to use the
// following technique to get round the issue:
// 1. Take the normal unix timestamp to form the whole number part of the
// output,
// 2. Take the result of the Nanosecond function, which retuns the offset
// within the second of the particular unix time instance, to form the
// decimal part of the output
// 3. Concatenate them to produce the final result
seconds := strconv.FormatInt(truncatedDate.Unix(), 10)
nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64)
output := append([]byte(seconds), []byte(nanosecondsOffset)[1:]...)
return output, nil
} }
// UnmarshalJSON is an implementation of the json.RawMessage interface and deserializses a // UnmarshalJSON is an implementation of the json.RawMessage interface and deserializses a

View File

@ -2,10 +2,11 @@ package jwt_test
import ( import (
"encoding/json" "encoding/json"
"math"
"testing" "testing"
"time" "time"
"github.com/golang-jwt/jwt/v4" "git.internal/re/jwt/v4"
) )
func TestNumericDate(t *testing.T) { func TestNumericDate(t *testing.T) {
@ -18,12 +19,10 @@ func TestNumericDate(t *testing.T) {
jwt.TimePrecision = time.Microsecond jwt.TimePrecision = time.Microsecond
raw := `{"iat":1516239022,"exp":1516239022.12345}` raw := `{"iat":1516239022.000000,"exp":1516239022.123450}`
err := json.Unmarshal([]byte(raw), &s) if err := json.Unmarshal([]byte(raw), &s); err != nil {
t.Fatalf("Unexpected error: %s", err)
if err != nil {
t.Errorf("Unexpected error: %s", err)
} }
b, _ := json.Marshal(s) b, _ := json.Marshal(s)
@ -42,7 +41,6 @@ func TestSingleArrayMarshal(t *testing.T) {
expected := `"test"` expected := `"test"`
b, err := json.Marshal(s) b, err := json.Marshal(s)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %s", err) t.Errorf("Unexpected error: %s", err)
} }
@ -65,3 +63,64 @@ func TestSingleArrayMarshal(t *testing.T) {
t.Errorf("Serialized format of string array mismatch. Expecting: %s Got: %s", string(expected), string(b)) t.Errorf("Serialized format of string array mismatch. Expecting: %s Got: %s", string(expected), string(b))
} }
} }
func TestNumericDate_MarshalJSON(t *testing.T) {
// Do not run this test in parallel because it's changing
// global state.
oldPrecision := jwt.TimePrecision
t.Cleanup(func() {
jwt.TimePrecision = oldPrecision
})
tt := []struct {
in time.Time
want string
precision time.Duration
}{
{time.Unix(5243700879, 0), "5243700879", time.Second},
{time.Unix(5243700879, 0), "5243700879.000", time.Millisecond},
{time.Unix(5243700879, 0), "5243700879.000000", time.Microsecond},
{time.Unix(5243700879, 0), "5243700879.000000000", time.Nanosecond},
//
{time.Unix(4239425898, 0), "4239425898", time.Second},
{time.Unix(4239425898, 0), "4239425898.000", time.Millisecond},
{time.Unix(4239425898, 0), "4239425898.000000", time.Microsecond},
{time.Unix(4239425898, 0), "4239425898.000000000", time.Nanosecond},
//
{time.Unix(253402271999, 0), "253402271999", time.Second},
{time.Unix(253402271999, 0), "253402271999.000", time.Millisecond},
{time.Unix(253402271999, 0), "253402271999.000000", time.Microsecond},
{time.Unix(253402271999, 0), "253402271999.000000000", time.Nanosecond},
//
{time.Unix(0, 1644285000210402000), "1644285000", time.Second},
{time.Unix(0, 1644285000210402000), "1644285000.210", time.Millisecond},
{time.Unix(0, 1644285000210402000), "1644285000.210402", time.Microsecond},
{time.Unix(0, 1644285000210402000), "1644285000.210402000", time.Nanosecond},
//
{time.Unix(0, 1644285315063096000), "1644285315", time.Second},
{time.Unix(0, 1644285315063096000), "1644285315.063", time.Millisecond},
{time.Unix(0, 1644285315063096000), "1644285315.063096", time.Microsecond},
{time.Unix(0, 1644285315063096000), "1644285315.063096000", time.Nanosecond},
// Maximum time that a go time.Time can represent
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807", time.Second},
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999", time.Millisecond},
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999999", time.Microsecond},
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999999999", time.Nanosecond},
// Strange precisions
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807", time.Second},
{time.Unix(math.MaxInt64, 999999999), "9223372036854775756", time.Minute},
{time.Unix(math.MaxInt64, 999999999), "9223372036854774016", time.Hour},
{time.Unix(math.MaxInt64, 999999999), "9223372036854745216", 24 * time.Hour},
}
for i, tc := range tt {
jwt.TimePrecision = tc.precision
by, err := jwt.NewNumericDate(tc.in).MarshalJSON()
if err != nil {
t.Fatal(err)
}
if got := string(by); got != tc.want {
t.Errorf("[%d]: failed encoding: got %q want %q", i, got, tc.want)
}
}
}