Repository: lestrrat-go/jwx

Issue:
Fix issues with godoclint or disable the linter
Hi @lestrrat! 👋

I noticed new PRs are failing the CI (e.g. [this](https://github.com/lestrrat-go/jwx/actions/runs/17999891891/job/51206582476?pr=1463#step:4:30)) due to the newly added linter, [`godoclint`](https://github.com/godoc-lint/godoc-lint) (authored by me). If you see value in keeping the new linter, I would be more than happy to contribute with a PR to fix the issues; otherwise, just ignore this issue.

Last but not least, huge thanks for this great module! 🙏


Discussion:
@lestrrat: Ah, I silenced this one a while back, but it still lives in an unmerged PR. That's why the errors.

On a good day I think I'd actually go and fix some of these, though I'd definitely silence it for internal packages (I could care less for things that the user is not supposed to see anyways... :). I'm still feeling a bit lazy after coming off of working as a conference staff, so I'll probably work on it one of these days. If you're game to work on it... sure, be my guest. If you do work on a PR, please be careful as there are more than a handful of generated files. Thanks.@babakks: Thanks, @lestrrat! I just made a PR to fix the issues. Not sure if I've actually touched auto-generated files (but I tried to look out for any hints around). I've also limited the linter's scope so that it doesn't check `internal` directories, nor at root nor nested.@babakks: The linter is still disabled due to the other PR (#1460), but I'll leave fixing that to you when you deem fit.@lestrrat: oh I thought I fixed it. I guess I shouldn't be commiting code outside from usual PC. will take a look later.

Solution patch:
diff --git a/.golangci.yml b/.golangci.yml
index 214a9eda..4ea45fdd 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -15,6 +15,7 @@ linters:
     - gocognit
     - gocritic
     - gocyclo
+    - godoclint
     - godot
     - godox
     - gosec
@@ -106,6 +107,9 @@ linters:
           - revive
         path: jwt/internal/types/
         text: "var-naming: avoid meaningless package names"
+      - linters:
+          - godoclint
+        path: (^|/)internal/
     paths:
       - third_party$
       - builtin$
diff --git a/Changes b/Changes
index 29910bf3..edea48f0 100644
--- a/Changes
+++ b/Changes
@@ -4,6 +4,25 @@ Changes
 v3 has many incompatibilities with v2. To see the full list of differences between
 v2 and v3, please read the Changes-v3.md file (https://github.com/lestrrat-go/jwx/blob/develop/v3/Changes-v3.md)
 
+v3.0.12 UNRELEASED
+  * [jws] Calling the deprecated `jws.NewSigner()` function for the time will cause
+    legacy signers to be loaded automatically. Previously, you had to explicitly
+    call `jws.Settings(jws.WithLegacySigners(true))` to enable legacy signers.
+
+    We incorrectly assumed that users would not be using `jws.NewSigner()`, and thus
+    disabled legacy signers by default. However, it turned out that some users
+    were using `jws.NewSigner()` in their code, which lead to breakages in 
+    existing code. In hindsight we should have known that any API made public before will
+    be used by _somebody_.
+
+    As a side effect, jws.Settings(jws.WithLegacySigners(...)) is now a no-op.
+
+    However, please do note that jws.Signer (and similar) objects were always intended to be
+    used for _registering_ new signing/verifying algorithms, and not for end users to actually
+    use them directly. If you are using them for other purposes, please consider changing
+    your code, as it is more than likely that we will somehow deprecate/remove/discouraged
+    their use in the future.
+
 v3.0.11 14 Sep 2025
   * [jwk] Add `(jwk.Cache).Shutdown()` method that delegates to the httprc controller
     object, to shutdown the cache.
diff --git a/docs/02-jws.md b/docs/02-jws.md
index da306f70..e57cf77d 100644
--- a/docs/02-jws.md
+++ b/docs/02-jws.md
@@ -632,63 +632,43 @@ import (
 )
 
 func Example_jws_custom_signer_verifier() {
-  // This example shows how to register external jws.Signer / jws.Verifier for
-  // a given algorithm.
-
-  for _, useLegacy := range []bool{false, true} {
-    if useLegacy {
-      // Legacy signer/verifier registration. DO NOT USE THIS IN NEW CODE.
-      if err := jws.RegisterSigner(jwa.EdDSA(), jws.SignerFactoryFn(LegacyNewCirclEdDSASigner)); err != nil {
-        fmt.Printf(`failed to register legacy signer: %s`, err)
-        return
-      }
-
-      if err := jws.RegisterVerifier(jwa.EdDSA(), jws.VerifierFactoryFn(LegacyNewCirclEdDSAVerifier)); err != nil {
-        fmt.Printf(`failed to register legacy verifier: %s`, err)
-        return
-      }
-    } else {
-      // Newer way of registering a custom signer/verifier
-      if err := jws.RegisterSigner(jwa.EdDSA(), CirclEdDSASigner{}); err != nil {
-        fmt.Printf(`failed to register signer: %s`, err)
-        return
-      }
-
-      if err := jws.RegisterVerifier(jwa.EdDSA(), CirclEdDSAVerifier{}); err != nil {
-        fmt.Printf(`failed to register verifier: %s`, err)
-        return
-      }
-    }
+  // Newer way of registering a custom signer/verifier
+  if err := jws.RegisterSigner(jwa.EdDSA(), CirclEdDSASigner{}); err != nil {
+    fmt.Printf(`failed to register signer: %s`, err)
+    return
+  }
 
-    pubkey, privkey, err := ed25519.GenerateKey(rand.Reader)
-    if err != nil {
-      fmt.Printf(`failed to generate keys: %s`, err)
-      return
-    }
+  if err := jws.RegisterVerifier(jwa.EdDSA(), CirclEdDSAVerifier{}); err != nil {
+    fmt.Printf(`failed to register verifier: %s`, err)
+    return
+  }
+  pubkey, privkey, err := ed25519.GenerateKey(rand.Reader)
+  if err != nil {
+    fmt.Printf(`failed to generate keys: %s`, err)
+    return
+  }
 
-    const payload = "Lorem Ipsum"
-    signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.EdDSA(), privkey))
-    if err != nil {
-      fmt.Printf(`failed to generate signed message: %s`, err)
-      return
-    }
+  const payload = "Lorem Ipsum"
+  signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.EdDSA(), privkey))
+  if err != nil {
+    fmt.Printf(`failed to generate signed message: %s`, err)
+    return
+  }
 
-    verified, err := jws.Verify(signed, jws.WithKey(jwa.EdDSA(), pubkey))
-    if err != nil {
-      fmt.Printf(`failed to verify signed message: %s`, err)
-      return
-    }
+  verified, err := jws.Verify(signed, jws.WithKey(jwa.EdDSA(), pubkey))
+  if err != nil {
+    fmt.Printf(`failed to verify signed message: %s`, err)
+    return
+  }
 
-    if string(verified) != payload {
-      fmt.Printf(`got invalid payload: %s`, verified)
-      return
-    }
+  if string(verified) != payload {
+    fmt.Printf(`got invalid payload: %s`, verified)
+    return
   }
+
   // OUTPUT:
   // Custom signer called
   // Custom verifier called
-  // Custom signer called (legacy)
-  // Custom verifier called (legacy)
 }
 
 type CirclEdDSASigner struct{}
diff --git a/formatkind_string_gen.go b/formatkind_string_gen.go
index 38abd1bc..ab728721 100644
--- a/formatkind_string_gen.go
+++ b/formatkind_string_gen.go
@@ -22,8 +22,9 @@ const _FormatKind_name = "InvalidFormatUnknownFormatJWEJWSJWKJWKSJWT"
 var _FormatKind_index = [...]uint8{0, 13, 26, 29, 32, 35, 39, 42}
 
 func (i FormatKind) String() string {
-	if i < 0 || i >= FormatKind(len(_FormatKind_index)-1) {
+	idx := int(i) - 0
+	if i < 0 || idx >= len(_FormatKind_index)-1 {
 		return "FormatKind(" + strconv.FormatInt(int64(i), 10) + ")"
 	}
-	return _FormatKind_name[_FormatKind_index[i]:_FormatKind_index[i+1]]
+	return _FormatKind_name[_FormatKind_index[idx]:_FormatKind_index[idx+1]]
 }
diff --git a/jwe/options.go b/jwe/options.go
index c9137eec..0437ea87 100644
--- a/jwe/options.go
+++ b/jwe/options.go
@@ -6,8 +6,9 @@ import (
 	"github.com/lestrrat-go/option/v2"
 )
 
-// Specify contents of the protected header. Some fields such as
-// "enc" and "zip" will be overwritten when encryption is performed.
+// WithProtectedHeaders is used to specify contents of the protected header.
+// Some fields such as "enc" and "zip" will be overwritten when encryption is
+// performed.
 //
 // There is no equivalent for unprotected headers in this implementation
 func WithProtectedHeaders(h Headers) EncryptOption {
diff --git a/jwk/cache.go b/jwk/cache.go
index b83b56c7..6d5b00f0 100644
--- a/jwk/cache.go
+++ b/jwk/cache.go
@@ -270,7 +270,7 @@ func (cs *cachedSet) cached() (Set, error) {
 	return cs.r.Resource(), nil
 }
 
-// Add is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
+// AddKey is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
 func (*cachedSet) AddKey(_ Key) error {
 	return fmt.Errorf(`(jwk.Cachedset).AddKey: jwk.CachedSet is immutable`)
 }
diff --git a/jwk/fetch.go b/jwk/fetch.go
index 910a2101..2c80a369 100644
--- a/jwk/fetch.go
+++ b/jwk/fetch.go
@@ -40,7 +40,7 @@ type CachedFetcher struct {
 	cache *Cache
 }
 
-// Creates a new `jwk.CachedFetcher` object.
+// NewCachedFetcher creates a new `jwk.CachedFetcher` object.
 func NewCachedFetcher(cache *Cache) *CachedFetcher {
 	return &CachedFetcher{cache}
 }
diff --git a/jwk/x509.go b/jwk/x509.go
index c0a7c4c4..f06063c6 100644
--- a/jwk/x509.go
+++ b/jwk/x509.go
@@ -118,7 +118,7 @@ func NewPEMDecoder() PEMDecoder {
 
 type pemDecoder struct{}
 
-// DecodePEM decodes a key in PEM encoded ASN.1 DER format.
+// Decode decodes a key in PEM encoded ASN.1 DER format.
 // and returns a raw key.
 func (pemDecoder) Decode(src []byte) (any, []byte, error) {
 	block, rest := pem.Decode(src)
diff --git a/jws/jws.go b/jws/jws.go
index 1fa77438..92a84e7f 100644
--- a/jws/jws.go
+++ b/jws/jws.go
@@ -586,11 +586,14 @@ func AlgorithmsForKey(key any) ([]jwa.SignatureAlgorithm, error) {
 	return algs, nil
 }
 
+// Settings allows you to set global settings for this JWS operations.
+//
+// Currently, the only setting available is `jws.WithLegacySigners()`,
+// which for various reason is now a no-op.
 func Settings(options ...GlobalOption) {
 	for _, option := range options {
 		switch option.Ident() {
 		case identLegacySigners{}:
-			enableLegacySigners()
 		}
 	}
 }
diff --git a/jws/jwsbb/header.go b/jws/jwsbb/header.go
index d50c38ee..cac3987e 100644
--- a/jws/jwsbb/header.go
+++ b/jws/jwsbb/header.go
@@ -26,7 +26,7 @@ func (e headerNotFoundError) Is(target error) bool {
 	}
 }
 
-// ErrHeaderdNotFound returns an error that can be passed to `errors.Is` to check if the error is
+// ErrHeaderNotFound returns an error that can be passed to `errors.Is` to check if the error is
 // the result of the field not being found
 func ErrHeaderNotFound() error {
 	return headerNotFoundError{}
diff --git a/jws/legacy.go b/jws/legacy.go
index a6687d68..767ad723 100644
--- a/jws/legacy.go
+++ b/jws/legacy.go
@@ -2,11 +2,14 @@ package jws
 
 import (
 	"fmt"
+	"sync"
 
 	"github.com/lestrrat-go/jwx/v3/jwa"
 	"github.com/lestrrat-go/jwx/v3/jws/legacy"
 )
 
+var enableLegacySignersOnce = &sync.Once{}
+
 func enableLegacySigners() {
 	for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} {
 		if err := RegisterSigner(alg, func(alg jwa.SignatureAlgorithm) SignerFactory {
@@ -74,7 +77,7 @@ func legacySignerFor(alg jwa.SignatureAlgorithm) (Signer, error) {
 	muSigner.Lock()
 	s, ok := signers[alg]
 	if !ok {
-		v, err := NewSigner(alg)
+		v, err := newLegacySigner(alg)
 		if err != nil {
 			muSigner.Unlock()
 			return nil, fmt.Errorf(`failed to create payload signer: %w`, err)
diff --git a/jws/legacy/legacy.go b/jws/legacy/legacy.go
index 84a25274..fe69b55e 100644
--- a/jws/legacy/legacy.go
+++ b/jws/legacy/legacy.go
@@ -23,7 +23,7 @@ type Signer interface {
 	Algorithm() jwa.SignatureAlgorithm
 }
 
-// This is for legacy support only.
+// Verifier is for legacy support only.
 type Verifier interface {
 	// Verify checks whether the payload and signature are valid for
 	// the given key.
diff --git a/jws/options.go b/jws/options.go
index 729e5619..4c217c34 100644
--- a/jws/options.go
+++ b/jws/options.go
@@ -38,7 +38,7 @@ type withKey struct {
 	public    Headers
 }
 
-// This exists as an escape hatch to modify the header values after the fact
+// Protected exists as an escape hatch to modify the header values after the fact
 func (w *withKey) Protected(v Headers) Headers {
 	if w.protected == nil && v != nil {
 		w.protected = v
@@ -221,7 +221,7 @@ type withInsecureNoSignature struct {
 	protected Headers
 }
 
-// This exists as an escape hatch to modify the header values after the fact
+// Protected exists as an escape hatch to modify the header values after the fact
 func (w *withInsecureNoSignature) Protected(v Headers) Headers {
 	if w.protected == nil && v != nil {
 		w.protected = v
diff --git a/jws/options.yaml b/jws/options.yaml
index 303ab3a3..79dbb725 100644
--- a/jws/options.yaml
+++ b/jws/options.yaml
@@ -227,8 +227,4 @@ options:
     interface: GlobalOption
     constant_value: true
     comment: |
-      WithLegacySigners specifies whether the JWS package should use legacy
-      signers for signing JWS messages. 
-      
-      Usually there's no need to use this option, as the new signers and
-      verifiers are loaded by default.
+      WithLegacySigners is a no-op option that exists only for backwards compatibility.
diff --git a/jws/options_gen.go b/jws/options_gen.go
index b97cf7e8..7013e86b 100644
--- a/jws/options_gen.go
+++ b/jws/options_gen.go
@@ -356,11 +356,7 @@ func WithKeyUsed(v any) VerifyOption {
 	return &verifyOption{option.New(identKeyUsed{}, v)}
 }
 
-// WithLegacySigners specifies whether the JWS package should use legacy
-// signers for signing JWS messages.
-//
-// Usually there's no need to use this option, as the new signers and
-// verifiers are loaded by default.
+// WithLegacySigners is a no-op option that exists only for backwards compatibility.
 func WithLegacySigners() GlobalOption {
 	return &globalOption{option.New(identLegacySigners{}, true)}
 }
diff --git a/jws/signer.go b/jws/signer.go
index 34066693..99005e85 100644
--- a/jws/signer.go
+++ b/jws/signer.go
@@ -2,6 +2,7 @@ package jws
 
 import (
 	"fmt"
+	"strings"
 	"sync"
 
 	"github.com/lestrrat-go/jwx/v3/jwa"
@@ -33,6 +34,19 @@ func (fn SignerFactoryFn) Create() (Signer, error) {
 	return fn()
 }
 
+func init() {
+	// register the signers using jwsbb. These will be used by default.
+	for _, alg := range jwa.SignatureAlgorithms() {
+		if alg == jwa.NoSignature() {
+			continue
+		}
+
+		if err := RegisterSigner(alg, defaultSigner{alg: alg}); err != nil {
+			panic(fmt.Sprintf("RegisterSigner failed: %v", err))
+		}
+	}
+}
+
 // SignerFor returns a Signer2 for the given signature algorithm.
 //
 // Currently, this function will never fail. It will always return a
@@ -43,6 +57,9 @@ func (fn SignerFactoryFn) Create() (Signer, error) {
 //  3. If no Signer2 or legacy Signer(Factory) is registered, it will return a
 //     default signer that uses jwsbb.Sign.
 //
+// 1 and 2 will take care of 99% of the cases. The only time 3 will happen is
+// when you are using a custom algorithm that is not supported out of the box.
+//
 // jwsbb.Sign knows how to handle a static set of algorithms, so if the
 // algorithm is not supported, it will return an error when you call
 // `Sign` on the default signer.
@@ -80,6 +97,14 @@ var signerDB = make(map[jwa.SignatureAlgorithm]SignerFactory)
 // Unlike the `UnregisterSigner` function, this function automatically
 // calls `jwa.RegisterSignatureAlgorithm` to register the algorithm
 // in this module's algorithm database.
+//
+// For backwards compatibility, this function also accepts
+// `SignerFactory` implementations, but this usage is deprecated.
+// You should use `Signer2` implementations instead.
+//
+// If you want to completely remove an algorithm, you must call
+// `jwa.UnregisterSignatureAlgorithm` yourself after calling
+// `UnregisterSigner`.
 func RegisterSigner(alg jwa.SignatureAlgorithm, f any) error {
 	jwa.RegisterSignatureAlgorithm(alg)
 	switch s := f.(type) {
@@ -87,22 +112,10 @@ func RegisterSigner(alg jwa.SignatureAlgorithm, f any) error {
 		muSigner2DB.Lock()
 		signer2DB[alg] = s
 		muSigner2DB.Unlock()
-
-		// delete the other signer, if there was one
-		muSignerDB.Lock()
-		delete(signerDB, alg)
-		muSignerDB.Unlock()
 	case SignerFactory:
 		muSignerDB.Lock()
 		signerDB[alg] = s
 		muSignerDB.Unlock()
-
-		// Remove previous signer, if there was one
-		removeSigner(alg)
-
-		muSigner2DB.Lock()
-		delete(signer2DB, alg)
-		muSigner2DB.Unlock()
 	default:
 		return fmt.Errorf(`jws.RegisterSigner: unsupported type %T for algorithm %q`, f, alg)
 	}
@@ -132,11 +145,25 @@ func UnregisterSigner(alg jwa.SignatureAlgorithm) {
 }
 
 // NewSigner creates a signer that signs payloads using the given signature algorithm.
-// This function is deprecated. You should use `SignerFor()` instead.
+// This function is deprecated, and will either be removed to re-purposed using
+// a different signature.
 //
-// This function only exists for backwards compatibility, but will not work
-// unless you enable the legacy support mode by calling jws.Settings(jws.WithLegacySigners(true)).
+// When you want to load a Signer object, you should use `SignerFor()` instead.
 func NewSigner(alg jwa.SignatureAlgorithm) (Signer, error) {
+	s, err := newLegacySigner(alg)
+	if err == nil {
+		return s, nil
+	}
+
+	if strings.HasPrefix(err.Error(), `jws.NewSigner: unsupported signature algorithm`) {
+		// When newLegacySigner fails, automatically trigger to enable signers
+		enableLegacySignersOnce.Do(enableLegacySigners)
+		return newLegacySigner(alg)
+	}
+	return nil, err
+}
+
+func newLegacySigner(alg jwa.SignatureAlgorithm) (Signer, error) {
 	muSignerDB.RLock()
 	f, ok := signerDB[alg]
 	muSignerDB.RUnlock()
diff --git a/jwt/openid/birthdate.go b/jwt/openid/birthdate.go
index aa7020a2..ce86b6a2 100644
--- a/jwt/openid/birthdate.go
+++ b/jwt/openid/birthdate.go
@@ -77,7 +77,7 @@ func parseBirthdayInt(s string) int {
 
 var birthdateRx = regexp.MustCompile(`^(\d{4})-(\d{2})-(\d{2})$`)
 
-// Accepts a value read from JSON, and converts it to a BirthdateClaim.
+// Accept accepts a value read from JSON, and converts it to a BirthdateClaim.
 // This method DOES NOT verify the correctness of a date.
 // Consumers should check for validity of dates such as Apr 31 et al
 func (b *BirthdateClaim) Accept(v any) error {
diff --git a/jwt/token_options.go b/jwt/token_options.go
index 0f54e056..088c4263 100644
--- a/jwt/token_options.go
+++ b/jwt/token_options.go
@@ -66,7 +66,7 @@ func (o *TokenOptionSet) Enable(flag TokenOption) {
 	*o = TokenOptionSet(o.Value() | uint64(flag))
 }
 
-// Enable sets the appropriate value to disable the option in the
+// Disable sets the appropriate value to disable the option in the
 // option set
 func (o *TokenOptionSet) Disable(flag TokenOption) {
 	*o = TokenOptionSet(o.Value() & ^uint64(flag))
diff --git a/jwt/token_options_gen.go b/jwt/token_options_gen.go
index 7e7cbf14..c1f333d1 100644
--- a/jwt/token_options_gen.go
+++ b/jwt/token_options_gen.go
@@ -17,9 +17,9 @@ const _TokenOption_name = "FlattenAudienceMaxPerTokenOption"
 var _TokenOption_index = [...]uint8{0, 15, 32}
 
 func (i TokenOption) String() string {
-	i -= 1
-	if i >= TokenOption(len(_TokenOption_index)-1) {
-		return "TokenOption(" + strconv.FormatInt(int64(i+1), 10) + ")"
+	idx := int(i) - 1
+	if i < 1 || idx >= len(_TokenOption_index)-1 {
+		return "TokenOption(" + strconv.FormatInt(int64(i), 10) + ")"
 	}
-	return _TokenOption_name[_TokenOption_index[i]:_TokenOption_index[i+1]]
+	return _TokenOption_name[_TokenOption_index[idx]:_TokenOption_index[idx+1]]
 }



Test patch:
diff --git a/examples/jws_custom_signer_verifier_example_test.go b/examples/jws_custom_signer_verifier_example_test.go
index 36a98499..3c4706fd 100644
--- a/examples/jws_custom_signer_verifier_example_test.go
+++ b/examples/jws_custom_signer_verifier_example_test.go
@@ -10,63 +10,43 @@ import (
 )
 
 func Example_jws_custom_signer_verifier() {
-	// This example shows how to register external jws.Signer / jws.Verifier for
-	// a given algorithm.
-
-	for _, useLegacy := range []bool{false, true} {
-		if useLegacy {
-			// Legacy signer/verifier registration. DO NOT USE THIS IN NEW CODE.
-			if err := jws.RegisterSigner(jwa.EdDSA(), jws.SignerFactoryFn(LegacyNewCirclEdDSASigner)); err != nil {
-				fmt.Printf(`failed to register legacy signer: %s`, err)
-				return
-			}
-
-			if err := jws.RegisterVerifier(jwa.EdDSA(), jws.VerifierFactoryFn(LegacyNewCirclEdDSAVerifier)); err != nil {
-				fmt.Printf(`failed to register legacy verifier: %s`, err)
-				return
-			}
-		} else {
-			// Newer way of registering a custom signer/verifier
-			if err := jws.RegisterSigner(jwa.EdDSA(), CirclEdDSASigner{}); err != nil {
-				fmt.Printf(`failed to register signer: %s`, err)
-				return
-			}
-
-			if err := jws.RegisterVerifier(jwa.EdDSA(), CirclEdDSAVerifier{}); err != nil {
-				fmt.Printf(`failed to register verifier: %s`, err)
-				return
-			}
-		}
+	// Newer way of registering a custom signer/verifier
+	if err := jws.RegisterSigner(jwa.EdDSA(), CirclEdDSASigner{}); err != nil {
+		fmt.Printf(`failed to register signer: %s`, err)
+		return
+	}
 
-		pubkey, privkey, err := ed25519.GenerateKey(rand.Reader)
-		if err != nil {
-			fmt.Printf(`failed to generate keys: %s`, err)
-			return
-		}
+	if err := jws.RegisterVerifier(jwa.EdDSA(), CirclEdDSAVerifier{}); err != nil {
+		fmt.Printf(`failed to register verifier: %s`, err)
+		return
+	}
+	pubkey, privkey, err := ed25519.GenerateKey(rand.Reader)
+	if err != nil {
+		fmt.Printf(`failed to generate keys: %s`, err)
+		return
+	}
 
-		const payload = "Lorem Ipsum"
-		signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.EdDSA(), privkey))
-		if err != nil {
-			fmt.Printf(`failed to generate signed message: %s`, err)
-			return
-		}
+	const payload = "Lorem Ipsum"
+	signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.EdDSA(), privkey))
+	if err != nil {
+		fmt.Printf(`failed to generate signed message: %s`, err)
+		return
+	}
 
-		verified, err := jws.Verify(signed, jws.WithKey(jwa.EdDSA(), pubkey))
-		if err != nil {
-			fmt.Printf(`failed to verify signed message: %s`, err)
-			return
-		}
+	verified, err := jws.Verify(signed, jws.WithKey(jwa.EdDSA(), pubkey))
+	if err != nil {
+		fmt.Printf(`failed to verify signed message: %s`, err)
+		return
+	}
 
-		if string(verified) != payload {
-			fmt.Printf(`got invalid payload: %s`, verified)
-			return
-		}
+	if string(verified) != payload {
+		fmt.Printf(`got invalid payload: %s`, verified)
+		return
 	}
+
 	// OUTPUT:
 	// Custom signer called
 	// Custom verifier called
-	// Custom signer called (legacy)
-	// Custom verifier called (legacy)
 }
 
 type CirclEdDSASigner struct{}
diff --git a/jws/jws_internal_test.go b/jws/jws_internal_test.go
new file mode 100644
index 00000000..d029926c
--- /dev/null
+++ b/jws/jws_internal_test.go
@@ -0,0 +1,83 @@
+package jws
+
+import (
+	"sync"
+	"testing"
+
+	"github.com/lestrrat-go/jwx/v3/jwa"
+	"github.com/lestrrat-go/jwx/v3/jws/legacy"
+	"github.com/stretchr/testify/require"
+)
+
+func wipeoutLegacySignatureRegistry() {
+	enableLegacySignersOnce = &sync.Once{}
+	signerDB = make(map[jwa.SignatureAlgorithm]SignerFactory)
+}
+
+func TestLegacySignatureSign(t *testing.T) {
+	// This tests using the legacy NewSigner API, WHICH SHOULD NOT BE USED ANYMORE.
+	// This test just ensures that the legacy API still works for those who, for some
+	// reason, decided to use this API.
+	t.Run("Test GH#1459", func(t *testing.T) {
+		// For this test, we need to do something naughty, which is to wipe out the
+		// legacy signature algorithm registry to make sure NewSigner works out
+		// of the box. This is why the test is in `jws` package, not `jws_test`.
+		wipeoutLegacySignatureRegistry()
+		require.Len(t, signerDB, 0, "signerDB should be empty")
+
+		// Test all supported signature algorithms using NewSigner. Note that NewSigner
+		// is a deprecated API. When the right time comes, we should remove this test.
+		for _, sig := range jwa.SignatureAlgorithms() {
+			if sig == jwa.NoSignature() {
+				// Skip alg=none
+				continue
+			}
+			signer, err := NewSigner(sig)
+			require.NoError(t, err, "NewSigner should succeed")
+			require.NotNil(t, signer, "NewSigner should return a valid signer")
+		}
+		require.NotEqual(t, len(signerDB), 0, "signerDB should not be empty anymore")
+	})
+	t.Run("use legacy signature algorithm registraiton API", func(t *testing.T) {
+		// Create a custom algorithm "HS256-legacy" for testing
+		hs256LegacyAlg := jwa.NewSignatureAlgorithm("HS256-legacy")
+
+		// Register the legacy HS256 signer factory
+		err := RegisterSigner(hs256LegacyAlg, SignerFactoryFn(func() (Signer, error) {
+			return legacy.NewHMACSigner(jwa.HS256()), nil
+		}))
+		require.NoError(t, err, "RegisterSigner should succeed")
+
+		// Clean up after test
+		t.Cleanup(func() {
+			UnregisterSigner(hs256LegacyAlg)
+			jwa.UnregisterSignatureAlgorithm(hs256LegacyAlg)
+		})
+
+		// Test data
+		key := []byte("test-secret-key-for-legacy-comparison")
+		payload := []byte("test payload for legacy signature comparison")
+
+		// Create a signed JWS using Sign with HS256 algorithm
+		signed, err := Sign(payload, WithKey(jwa.HS256(), key))
+		require.NoError(t, err, "Sign should succeed")
+
+		// Parse the JWS to extract the signature
+		msg, err := Parse(signed)
+		require.NoError(t, err, "Parse should succeed")
+		require.Len(t, msg.Signatures(), 1, "should have one signature")
+
+		jwsSignature := msg.Signatures()[0].Signature()
+
+		// Create a signature using Signature.Sign() with HS256-legacy algorithm
+		sig := NewSignature()
+		legacySigner, err := NewSigner(hs256LegacyAlg)
+		require.NoError(t, err, "NewSigner should succeed for legacy algorithm")
+
+		rawSig, _, err := sig.Sign(payload, legacySigner, key)
+		require.NoError(t, err, "Signature.Sign should succeed")
+
+		// Compare the signatures - they should be identical
+		require.Equal(t, jwsSignature, rawSig, "signatures from Sign and Signature.Sign should be identical")
+	})
+}
diff --git a/jws/jws_test.go b/jws/jws_test.go
index 8446f06d..c0509999 100644
--- a/jws/jws_test.go
+++ b/jws/jws_test.go
@@ -31,7 +31,6 @@ import (
 	"github.com/lestrrat-go/jwx/v3/jwa"
 	"github.com/lestrrat-go/jwx/v3/jwk"
 	"github.com/lestrrat-go/jwx/v3/jws"
-	"github.com/lestrrat-go/jwx/v3/jws/legacy"
 	"github.com/lestrrat-go/jwx/v3/jwt"
 	"github.com/stretchr/testify/require"
 )
@@ -1458,13 +1457,17 @@ func TestGH910(t *testing.T) {
 	jws.RegisterSigner(sha256Algo, jws.SignerFactoryFn(func() (jws.Signer, error) {
 		return s256SignerVerifier{}, nil
 	}))
-	defer jws.UnregisterSigner(sha256Algo)
+	t.Cleanup(func() {
+		jws.UnregisterSigner(sha256Algo)
+	})
 
 	jws.RegisterVerifier(sha256Algo, jws.VerifierFactoryFn(func() (jws.Verifier, error) {
 		return s256SignerVerifier{}, nil
 	}))
-	defer jws.UnregisterVerifier(sha256Algo)
-	defer jwa.UnregisterSignatureAlgorithm(sha256Algo)
+	t.Cleanup(func() {
+		jws.UnregisterVerifier(sha256Algo)
+		jwa.UnregisterSignatureAlgorithm(sha256Algo)
+	})
 
 	// Now that we have established that the signature algorithm works,
 	// we can proceed with the test
@@ -1720,45 +1723,3 @@ func BenchmarkSplitCompatString(b *testing.B) {
 		}
 	}
 }
-
-func TestLegacySignatureSign(t *testing.T) {
-	// Create a custom algorithm "HS256-legacy" for testing
-	hs256LegacyAlg := jwa.NewSignatureAlgorithm("HS256-legacy")
-
-	// Register the legacy HS256 signer factory
-	err := jws.RegisterSigner(hs256LegacyAlg, jws.SignerFactoryFn(func() (jws.Signer, error) {
-		return legacy.NewHMACSigner(jwa.HS256()), nil
-	}))
-	require.NoError(t, err, "RegisterSigner should succeed")
-
-	// Clean up after test
-	t.Cleanup(func() {
-		jws.UnregisterSigner(hs256LegacyAlg)
-	})
-
-	// Test data
-	key := []byte("test-secret-key-for-legacy-comparison")
-	payload := []byte("test payload for legacy signature comparison")
-
-	// Create a signed JWS using jws.Sign with HS256 algorithm
-	signed, err := jws.Sign(payload, jws.WithKey(jwa.HS256(), key))
-	require.NoError(t, err, "jws.Sign should succeed")
-
-	// Parse the JWS to extract the signature
-	msg, err := jws.Parse(signed)
-	require.NoError(t, err, "jws.Parse should succeed")
-	require.Len(t, msg.Signatures(), 1, "should have one signature")
-
-	jwsSignature := msg.Signatures()[0].Signature()
-
-	// Create a signature using jws.Signature.Sign() with HS256-legacy algorithm
-	sig := jws.NewSignature()
-	legacySigner, err := jws.NewSigner(hs256LegacyAlg)
-	require.NoError(t, err, "jws.NewSigner should succeed for legacy algorithm")
-
-	rawSig, _, err := sig.Sign(payload, legacySigner, key)
-	require.NoError(t, err, "jws.Signature.Sign should succeed")
-
-	// Compare the signatures - they should be identical
-	require.Equal(t, jwsSignature, rawSig, "signatures from jws.Sign and jws.Signature.Sign should be identical")
-}