Repository: lestrrat-go/jwx
Pull request #1073 (2024-02-08 07:17:27)

Bump golang.org/x/crypto from 0.18.0 to 0.19.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.18.0 to 0.19.0.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/golang/crypto/commit/405cb3bdea78b1b48ee79096733841247a944de0"><code>405cb3b</code></a> go.mod: update golang.org/x dependencies</li>
<li><a href="https://github.com/golang/crypto/commit/913d3ae7415ace5edad6750b7c9192ac7518e9df"><code>913d3ae</code></a> x509roots/fallback: update bundle</li>
<li>See full diff in <a href="https://github.com/golang/crypto/compare/v0.18.0...v0.19.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/crypto&package-manager=go_modules&previous-version=0.18.0&new-version=0.19.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 73fbb900..11ba5ba5 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -10,7 +10,7 @@ jobs:
         with:
           go-version: 1.19
           check-latest: true
-      - uses: golangci/golangci-lint-action@v3
+      - uses: golangci/golangci-lint-action@v4
         with:
           version: v1.54.2
       - name: Run go vet
diff --git a/Changes b/Changes
index 7c6ae61b..e1793867 100644
--- a/Changes
+++ b/Changes
@@ -6,9 +6,18 @@ v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jw
 
 v2.0.20 UNRELEASED
 [New Features]
-  * [jwe] Added jwe.Settings(WithMaxBufferSize(int64)) to set the maximum size of
+  * [jwe] Added `jwe.Settings(WithMaxBufferSize(int64))` to set the maximum size of
     internal buffers. The default value is 256MB. Most users do not need to change
     this value.
+  * [jws] Allow `jws.WithCompact()` and `jws.WithJSON()` to be passed to `jws.Parse()` and
+    `jws.Verify()`. These options control the expected serialization format for the
+    JWS message.
+  * [jwt] Add `jwt.WithCompactOnly()` to specify that only compact serialization can
+    be used for `jwt.Parse()`. Previously, by virtue of `jws.Parse()` allowing either
+    JSON or Compact serialization format, `jwt.Parse()` also alloed JSON serialization
+    where as RFC7519 explicitly states that only compact serialization should be
+    used. For backward compatibility the default behavior is not changed, but you
+    can set this global option for jwt: `jwt.Settings(jwt.WithCompactOnly(true))`
 
 [Miscellaneous]
   * Internal key conversions should now allow private keys to be used in place of
diff --git a/deps.bzl b/deps.bzl
index 0204ed5c..badb900a 100644
--- a/deps.bzl
+++ b/deps.bzl
@@ -115,8 +115,8 @@ def go_dependencies():
         name = "org_golang_x_crypto",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/crypto",
-        sum = "h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=",
-        version = "v0.18.0",
+        sum = "h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=",
+        version = "v0.19.0",
     )
 
     go_repository(
@@ -131,15 +131,15 @@ def go_dependencies():
         name = "org_golang_x_sys",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/sys",
-        sum = "h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=",
-        version = "v0.16.0",
+        sum = "h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=",
+        version = "v0.17.0",
     )
     go_repository(
         name = "org_golang_x_term",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/term",
-        sum = "h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=",
-        version = "v0.16.0",
+        sum = "h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=",
+        version = "v0.17.0",
     )
 
     go_repository(
diff --git a/examples/go.sum b/examples/go.sum
index fea8efdc..2e51a0e1 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -36,8 +36,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
-golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -57,14 +57,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
-golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
diff --git a/go.mod b/go.mod
index b6156e82..16d72ad6 100644
--- a/go.mod
+++ b/go.mod
@@ -11,13 +11,13 @@ require (
 	github.com/lestrrat-go/option v1.0.1
 	github.com/segmentio/asm v1.2.0
 	github.com/stretchr/testify v1.8.4
-	golang.org/x/crypto v0.18.0
+	golang.org/x/crypto v0.19.0
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/lestrrat-go/httpcc v1.0.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	golang.org/x/sys v0.16.0 // indirect
+	golang.org/x/sys v0.17.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index e3210bba..fda61a01 100644
--- a/go.sum
+++ b/go.sum
@@ -24,10 +24,10 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
-golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/jws/jws.go b/jws/jws.go
index 4a9e8194..096e4f22 100644
--- a/jws/jws.go
+++ b/jws/jws.go
@@ -103,7 +103,7 @@ func makeSigner(alg jwa.SignatureAlgorithm, key interface{}, public, protected H
 }
 
 const (
-	fmtInvalid = iota
+	fmtInvalid = 1 << iota
 	fmtCompact
 	fmtJSON
 	fmtJSONPretty
@@ -314,6 +314,7 @@ var allowNoneWhitelist = jwk.WhitelistFunc(func(string) bool {
 // accept messages with "none" signature algorithm, use `jws.Parse` to get the
 // raw JWS message.
 func Verify(buf []byte, options ...VerifyOption) ([]byte, error) {
+	var parseOptions []ParseOption
 	var dst *Message
 	var detachedPayload []byte
 	var keyProviders []KeyProvider
@@ -347,6 +348,8 @@ func Verify(buf []byte, options ...VerifyOption) ([]byte, error) {
 			ctx = option.Value().(context.Context)
 		case identValidateKey{}:
 			validateKey = option.Value().(bool)
+		case identSerialization{}:
+			parseOptions = append(parseOptions, option.(ParseOption))
 		default:
 			return nil, fmt.Errorf(`invalid jws.VerifyOption %q passed`, `With`+strings.TrimPrefix(fmt.Sprintf(`%T`, option.Ident()), `jws.ident`))
 		}
@@ -356,7 +359,7 @@ func Verify(buf []byte, options ...VerifyOption) ([]byte, error) {
 		return nil, fmt.Errorf(`jws.Verify: no key providers have been provided (see jws.WithKey(), jws.WithKeySet(), jws.WithVerifyAuto(), and jws.WithKeyProvider()`)
 	}
 
-	msg, err := Parse(buf)
+	msg, err := Parse(buf, parseOptions...)
 	if err != nil {
 		return nil, fmt.Errorf(`failed to parse jws: %w`, err)
 	}
@@ -523,23 +526,47 @@ func readAll(rdr io.Reader) ([]byte, bool) {
 }
 
 // Parse parses contents from the given source and creates a jws.Message
-// struct. The input can be in either compact or full JSON serialization.
+// struct. By default the input can be in either compact or full JSON serialization.
 //
-// Parse() currently does not take any options, but the API accepts it
-// in anticipation of future addition.
-func Parse(src []byte, _ ...ParseOption) (*Message, error) {
-	for i := 0; i < len(src); i++ {
-		r := rune(src[i])
-		if r >= utf8.RuneSelf {
-			r, _ = utf8.DecodeRune(src)
+// You may pass `jws.WithJSON()` and/or `jws.WithCompact()` to specify
+// explicitly which format to use. If neither or both is specified, the function
+// will attempt to autodetect the format. If one or the other is specified,
+// only the specified format will be attempted.
+func Parse(src []byte, options ...ParseOption) (*Message, error) {
+	var formats int
+	for _, option := range options {
+		//nolint:forcetypeassert
+		switch option.Ident() {
+		case identSerialization{}:
+			switch option.Value().(int) {
+			case fmtJSON:
+				formats |= fmtJSON
+			case fmtCompact:
+				formats |= fmtCompact
+			}
 		}
-		if !unicode.IsSpace(r) {
-			if r == '{' {
-				return parseJSON(src)
+	}
+
+	// if format is 0 or both JSON/Compact, auto detect
+	if v := formats & (fmtJSON | fmtCompact); v == 0 || v == fmtJSON|fmtCompact {
+		for i := 0; i < len(src); i++ {
+			r := rune(src[i])
+			if r >= utf8.RuneSelf {
+				r, _ = utf8.DecodeRune(src)
+			}
+			if !unicode.IsSpace(r) {
+				if r == '{' {
+					return parseJSON(src)
+				}
+				return parseCompact(src)
 			}
-			return parseCompact(src)
 		}
+	} else if formats&fmtCompact == fmtCompact {
+		return parseCompact(src)
+	} else if formats&fmtJSON == fmtJSON {
+		return parseJSON(src)
 	}
+
 	return nil, fmt.Errorf(`invalid byte sequence`)
 }
 
diff --git a/jws/options.go b/jws/options.go
index e374a85d..a6cc472c 100644
--- a/jws/options.go
+++ b/jws/options.go
@@ -20,7 +20,7 @@ func WithHeaders(h Headers) SignOption {
 //
 // If you pass multiple keys to `jws.Sign()`, it will fail unless
 // you also pass this option.
-func WithJSON(options ...WithJSONSuboption) SignOption {
+func WithJSON(options ...WithJSONSuboption) SignVerifyParseOption {
 	var pretty bool
 	for _, option := range options {
 		//nolint:forcetypeassert
@@ -34,7 +34,7 @@ func WithJSON(options ...WithJSONSuboption) SignOption {
 	if pretty {
 		format = fmtJSONPretty
 	}
-	return &signOption{option.New(identSerialization{}, format)}
+	return &signVerifyParseOption{option.New(identSerialization{}, format)}
 }
 
 type withKey struct {
diff --git a/jws/options.yaml b/jws/options.yaml
index 65b5352c..75a2de82 100644
--- a/jws/options.yaml
+++ b/jws/options.yaml
@@ -7,6 +7,9 @@ interfaces:
   - name: VerifyOption
     comment: |
       VerifyOption describes options that can be passed to `jws.Verify`
+    methods:
+      - verifyOption
+      - parseOption
   - name: SignOption
     comment: |
       SignOption describes options that can be passed to `jws.Sign`
@@ -14,6 +17,7 @@ interfaces:
     methods:
       - signOption
       - verifyOption
+      - parseOption
     comment: |
       SignVerifyOption describes options that can be passed to either `jws.Verify` or `jws.Sign`
   - name: WithJSONSuboption
@@ -35,6 +39,12 @@ interfaces:
   - name: ReadFileOption
     comment: |
       ReadFileOption is a type of `Option` that can be passed to `jws.ReadFile`
+  - name: SignVerifyParseOption
+    methods:
+      - signOption
+      - verifyOption
+      - parseOption
+      - readFileOption
 options:
   - ident: Key
     skip_option: true
@@ -42,7 +52,7 @@ options:
     skip_option: true
   - ident: Serialization
     option_name: WithCompact
-    interface: SignOption
+    interface: SignVerifyParseOption
     constant_value: fmtCompact
     comment: |
       WithCompact specifies that the result of `jws.Sign()` is serialized in
diff --git a/jws/options_gen.go b/jws/options_gen.go
index ca834e10..fbef1ef3 100644
--- a/jws/options_gen.go
+++ b/jws/options_gen.go
@@ -64,6 +64,7 @@ type SignVerifyOption interface {
 	Option
 	signOption()
 	verifyOption()
+	parseOption()
 }
 
 type signVerifyOption struct {
@@ -74,10 +75,33 @@ func (*signVerifyOption) signOption() {}
 
 func (*signVerifyOption) verifyOption() {}
 
+func (*signVerifyOption) parseOption() {}
+
+type SignVerifyParseOption interface {
+	Option
+	signOption()
+	verifyOption()
+	parseOption()
+	readFileOption()
+}
+
+type signVerifyParseOption struct {
+	Option
+}
+
+func (*signVerifyParseOption) signOption() {}
+
+func (*signVerifyParseOption) verifyOption() {}
+
+func (*signVerifyParseOption) parseOption() {}
+
+func (*signVerifyParseOption) readFileOption() {}
+
 // VerifyOption describes options that can be passed to `jws.Verify`
 type VerifyOption interface {
 	Option
 	verifyOption()
+	parseOption()
 }
 
 type verifyOption struct {
@@ -86,6 +110,8 @@ type verifyOption struct {
 
 func (*verifyOption) verifyOption() {}
 
+func (*verifyOption) parseOption() {}
+
 // JSONSuboption describes suboptions that can be passed to `jws.WithJSON()` option
 type WithJSONSuboption interface {
 	Option
@@ -329,8 +355,8 @@ func WithRequireKid(v bool) WithKeySetSuboption {
 //
 // By default `jws.Sign()` will opt to use compact format, so you usually
 // do not need to specify this option other than to be explicit about it
-func WithCompact() SignOption {
-	return &signOption{option.New(identSerialization{}, fmtCompact)}
+func WithCompact() SignVerifyParseOption {
+	return &signVerifyParseOption{option.New(identSerialization{}, fmtCompact)}
 }
 
 // WithUseDefault specifies that if and only if a jwk.Key contains
diff --git a/jwt/jwt.go b/jwt/jwt.go
index fa0c2021..c09ea8bf 100644
--- a/jwt/jwt.go
+++ b/jwt/jwt.go
@@ -17,6 +17,7 @@ import (
 	"github.com/lestrrat-go/jwx/v2/jwt/internal/types"
 )
 
+var compactOnly uint32
 var errInvalidJWT = errors.New(`invalid JWT`)
 
 // ErrInvalidJWT returns the opaque error value that is returned when
@@ -28,7 +29,8 @@ func ErrInvalidJWT() error {
 
 // Settings controls global settings that are specific to JWTs.
 func Settings(options ...GlobalOption) {
-	var flattenAudienceBool bool
+	var flattenAudience bool
+	var compactOnlyBool bool
 	var parsePedantic bool
 	var parsePrecision = types.MaxPrecision + 1  // illegal value, so we can detect nothing was set
 	var formatPrecision = types.MaxPrecision + 1 // illegal value, so we can detect nothing was set
@@ -37,7 +39,9 @@ func Settings(options ...GlobalOption) {
 	for _, option := range options {
 		switch option.Ident() {
 		case identFlattenAudience{}:
-			flattenAudienceBool = option.Value().(bool)
+			flattenAudience = option.Value().(bool)
+		case identCompactOnly{}:
+			compactOnlyBool = option.Value().(bool)
 		case identNumericDateParsePedantic{}:
 			parsePedantic = option.Value().(bool)
 		case identNumericDateParsePrecision{}:
@@ -80,9 +84,20 @@ func Settings(options ...GlobalOption) {
 		}
 	}
 
+	{
+		v := atomic.LoadUint32(&compactOnly)
+		if (v == 1) != compactOnlyBool {
+			var newVal uint32
+			if compactOnlyBool {
+				newVal = 1
+			}
+			atomic.CompareAndSwapUint32(&compactOnly, v, newVal)
+		}
+	}
+
 	{
 		defaultOptionsMu.Lock()
-		if flattenAudienceBool {
+		if flattenAudience {
 			defaultOptions.Enable(FlattenAudience)
 		} else {
 			defaultOptions.Disable(FlattenAudience)
@@ -244,7 +259,11 @@ func verifyJWS(ctx *parseCtx, payload []byte) ([]byte, int, error) {
 		return nil, _JwsVerifySkipped, nil
 	}
 
-	verified, err := jws.Verify(payload, ctx.verifyOpts...)
+	verifyOpts := ctx.verifyOpts
+	if atomic.LoadUint32(&compactOnly) == 1 {
+		verifyOpts = append(verifyOpts, jws.WithCompact())
+	}
+	verified, err := jws.Verify(payload, verifyOpts...)
 	return verified, _JwsVerifyDone, err
 }
 
@@ -330,7 +349,11 @@ OUTER:
 			}
 
 			// No verification.
-			m, err := jws.Parse(data)
+			var parseOptions []jws.ParseOption
+			if atomic.LoadUint32(&compactOnly) == 1 {
+				parseOptions = append(parseOptions, jws.WithCompact())
+			}
+			m, err := jws.Parse(data, parseOptions...)
 			if err != nil {
 				return nil, fmt.Errorf(`invalid jws message: %w`, err)
 			}
diff --git a/jwt/options.yaml b/jwt/options.yaml
index 2a11b9b4..5e257801 100644
--- a/jwt/options.yaml
+++ b/jwt/options.yaml
@@ -82,6 +82,16 @@ options:
 
       See the documentation for `jwt.TokenOptionSet`, `(jwt.Token).Options`, and
       `jwt.FlattenAudience` for more details
+  - ident: CompactOnly
+    interface: GlobalOption
+    argument_type: bool
+    comment: |
+      WithCompactOnly option controls whether jwt.Parse should accept only tokens
+      that are in compact serialization format. RFC7519 specifies that JWTs
+      should be serialized in JWS compact form only, but historically this library
+      allowed for deserialization of JWTs in JWS's JSON serialization format.
+      Specifying this option will disable this behavior, and will report
+      errots if the token is not in compact serialization format.
   - ident: FormKey
     interface: ParseOption
     argument_type: string
diff --git a/jwt/options_gen.go b/jwt/options_gen.go
index ebde2d61..375d704e 100644
--- a/jwt/options_gen.go
+++ b/jwt/options_gen.go
@@ -124,6 +124,7 @@ func (*validateOption) validateOption() {}
 
 type identAcceptableSkew struct{}
 type identClock struct{}
+type identCompactOnly struct{}
 type identContext struct{}
 type identEncryptOption struct{}
 type identFS struct{}
@@ -150,6 +151,10 @@ func (identClock) String() string {
 	return "WithClock"
 }
 
+func (identCompactOnly) String() string {
+	return "WithCompactOnly"
+}
+
 func (identContext) String() string {
 	return "WithContext"
 }
@@ -230,6 +235,16 @@ func WithClock(v Clock) ValidateOption {
 	return &validateOption{option.New(identClock{}, v)}
 }
 
+// WithCompactOnly option controls whether jwt.Parse should accept only tokens
+// that are in compact serialization format. RFC7519 specifies that JWTs
+// should be serialized in JWS compact form only, but historically this library
+// allowed for deserialization of JWTs in JWS's JSON serialization format.
+// Specifying this option will disable this behavior, and will report
+// errots if the token is not in compact serialization format.
+func WithCompactOnly(v bool) GlobalOption {
+	return &globalOption{option.New(identCompactOnly{}, v)}
+}
+
 // WithContext allows you to specify a context.Context object to be used
 // with `jwt.Validate()` option.
 //


Test patch:
diff --git a/jws/jws_test.go b/jws/jws_test.go
index 04fdb9d9..ecf54085 100644
--- a/jws/jws_test.go
+++ b/jws/jws_test.go
@@ -1887,3 +1887,53 @@ func TestEmptyProtectedField(t *testing.T) {
 	_, err = jws.Parse(invalidMessage)
 	require.Error(t, err, `jws.Parse should fail`)
 }
+
+func TestParseFormat(t *testing.T) {
+	privKey, err := jwxtest.GenerateRsaJwk()
+	require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`)
+
+	signedCompact, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256, privKey), jws.WithValidateKey(true))
+	require.NoError(t, err, `jws.Sign should succeed`)
+
+	signedJSON, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256, privKey), jws.WithValidateKey(true), jws.WithJSON())
+	require.NoError(t, err, `jws.Sign should succeed`)
+
+	// Only compact formats should succeed
+	_, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256, privKey), jws.WithCompact())
+	require.NoError(t, err, `jws.Verify should succeed`)
+	_, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256, privKey), jws.WithCompact())
+	require.Error(t, err, `jws.Verify should fail`)
+	_, err = jws.Parse(signedCompact, jws.WithCompact())
+	require.NoError(t, err, `jws.Parse should succeed`)
+	_, err = jws.Parse(signedJSON, jws.WithCompact())
+	require.Error(t, err, `jws.Parse should fail`)
+
+	// Only JSON formats should succeed
+	_, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256, privKey), jws.WithJSON())
+	require.Error(t, err, `jws.Verify should fail`)
+	_, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256, privKey), jws.WithJSON())
+	require.NoError(t, err, `jws.Verify should succeed`)
+	_, err = jws.Parse(signedJSON, jws.WithJSON())
+	require.NoError(t, err, `jws.Parse should succeed`)
+	_, err = jws.Parse(signedCompact, jws.WithJSON())
+	require.Error(t, err, `jws.Parse should fail`)
+
+	// Either format should succeed
+	_, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256, privKey))
+	require.NoError(t, err, `jws.Verify should succeed`)
+	_, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256, privKey), jws.WithJSON(), jws.WithCompact())
+	require.NoError(t, err, `jws.Verify should succeed`)
+	_, err = jws.Parse(signedCompact)
+	require.NoError(t, err, `jws.Parse should succeed`)
+	_, err = jws.Parse(signedCompact, jws.WithJSON(), jws.WithCompact())
+	require.NoError(t, err, `jws.Parse should succeed`)
+
+	_, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256, privKey))
+	require.NoError(t, err, `jws.Verify should succeed`)
+	_, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256, privKey), jws.WithJSON(), jws.WithCompact())
+	require.NoError(t, err, `jws.Verify should succeed`)
+	_, err = jws.Parse(signedJSON)
+	require.NoError(t, err, `jws.Parse should succeed`)
+	_, err = jws.Parse(signedJSON, jws.WithJSON(), jws.WithCompact())
+	require.NoError(t, err, `jws.Parse should succeed`)
+}
diff --git a/jwt/jwt_test.go b/jwt/jwt_test.go
index 8e94faa3..86f323d7 100644
--- a/jwt/jwt_test.go
+++ b/jwt/jwt_test.go
@@ -1773,3 +1773,33 @@ func TestGH1007(t *testing.T) {
 	_, err = jwt.ParseInsecure(signed, jwt.WithKey(jwa.RS256, wrongPubKey))
 	require.NoError(t, err, `jwt.ParseInsecure with jwt.WithKey() should succeed`)
 }
+
+func TestParseJSON(t *testing.T) {
+	// NOTE: jwt.Settings has global effect!
+	defer jwt.Settings(jwt.WithCompactOnly(false))
+	for _, compactOnly := range []bool{true, false} {
+		t.Run("compactOnly="+strconv.FormatBool(compactOnly), func(t *testing.T) {
+			jwt.Settings(jwt.WithCompactOnly(compactOnly))
+
+			privKey, err := jwxtest.GenerateRsaJwk()
+			require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`)
+
+			signedJSON, err := jws.Sign([]byte(`{}`), jws.WithKey(jwa.RS256, privKey), jws.WithValidateKey(true), jws.WithJSON())
+			require.NoError(t, err, `jws.Sign should succeed`)
+
+			// jws.Verify should succeed
+			_, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256, privKey))
+			require.NoError(t, err, `jws.Parse should succeed`)
+
+			if compactOnly {
+				// jwt.Parse should fail
+				_, err = jwt.Parse(signedJSON, jwt.WithKey(jwa.RS256, privKey))
+				require.Error(t, err, `jws.Parse should fail`)
+			} else {
+				// for backward compatibility, this should succeed
+				_, err = jwt.Parse(signedJSON, jwt.WithKey(jwa.RS256, privKey))
+				require.NoError(t, err, `jws.Parse should succeed`)
+			}
+		})
+	}
+}
diff --git a/jwt/options_gen_test.go b/jwt/options_gen_test.go
index bf7b55ed..1ad69c2b 100644
--- a/jwt/options_gen_test.go
+++ b/jwt/options_gen_test.go
@@ -11,6 +11,7 @@ import (
 func TestOptionIdent(t *testing.T) {
 	require.Equal(t, "WithAcceptableSkew", identAcceptableSkew{}.String())
 	require.Equal(t, "WithClock", identClock{}.String())
+	require.Equal(t, "WithCompactOnly", identCompactOnly{}.String())
 	require.Equal(t, "WithContext", identContext{}.String())
 	require.Equal(t, "WithEncryptOption", identEncryptOption{}.String())
 	require.Equal(t, "WithFS", identFS{}.String())