Repository: project-zot/zot

Issue:
[Feat]: Support EC public key
### Is your feature request related to a problem? Please describe.

Currently, trying to integrate auth login with external server bearer token, I receive this error
```
{"level":"panic","error":"Key is not a valid RSA public key","goroutine":1,"caller":"zotregistry.dev/zot/pkg/api/authn.go:426","time":"2024-08-03T13:52:30.053939244Z","message":"failed to create bearer authorizer"}
```
because it seems that Zot does not support EC (not RSA) public keys.

### Describe the solution you'd like

_No response_

### Describe alternatives you've considered

_No response_

### Additional context

_No response_

Hints from issue comments:
@rchincha: @giovannipapini we use chart-museum as our library for this, which currently supports only RSA. Let us look into this.@rchincha: https://github.com/project-zot/zot/pull/2998

has been merged

Solution patch:
diff --git a/errors/errors.go b/errors/errors.go
index b49c0f87..d64dcc9b 100644
--- a/errors/errors.go
+++ b/errors/errors.go
@@ -175,4 +175,8 @@ var (
 	ErrImageNotFound                  = errors.New("image not found")
 	ErrAmbiguousInput                 = errors.New("input is not specific enough")
 	ErrReceivedUnexpectedAuthHeader   = errors.New("received unexpected www-authenticate header")
+	ErrNoBearerToken                  = errors.New("no bearer token given")
+	ErrInvalidBearerToken             = errors.New("invalid bearer token given")
+	ErrInsufficientScope              = errors.New("bearer token does not have sufficient scope")
+	ErrCouldNotLoadCertificate        = errors.New("failed to load certificate")
 )
diff --git a/go.mod b/go.mod
index 1ecaad11..e7aae618 100644
--- a/go.mod
+++ b/go.mod
@@ -33,6 +33,7 @@ require (
 	github.com/go-redis/redismock/v9 v9.2.0
 	github.com/go-redsync/redsync/v4 v4.13.0
 	github.com/gofrs/uuid v4.4.0+incompatible
+	github.com/golang-jwt/jwt/v5 v5.2.1
 	github.com/google/go-containerregistry v0.20.3
 	github.com/google/go-github/v62 v62.0.0
 	github.com/google/uuid v1.6.0
@@ -273,7 +274,6 @@ require (
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
 	github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
-	github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
 	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
diff --git a/pkg/api/authn.go b/pkg/api/authn.go
index 947f8e79..7435c88e 100644
--- a/pkg/api/authn.go
+++ b/pkg/api/authn.go
@@ -6,17 +6,16 @@ import (
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/hex"
+	"encoding/pem"
 	"errors"
 	"fmt"
 	"net"
 	"net/http"
 	"os"
-	"regexp"
 	"strconv"
 	"strings"
 	"time"
 
-	"github.com/chartmuseum/auth"
 	guuid "github.com/gofrs/uuid"
 	"github.com/google/go-github/v62/github"
 	"github.com/google/uuid"
@@ -39,9 +38,8 @@ import (
 )
 
 const (
-	bearerAuthDefaultAccessEntryType = "repository"
-	issuedAtOffset                   = 5 * time.Second
-	relyingPartyCookieMaxAge         = 120
+	issuedAtOffset           = 5 * time.Second
+	relyingPartyCookieMaxAge = 120
 )
 
 type AuthnMiddleware struct {
@@ -404,17 +402,17 @@ func (amw *AuthnMiddleware) tryAuthnHandlers(ctlr *Controller) mux.MiddlewareFun
 }
 
 func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
-	authorizer, err := auth.NewAuthorizer(&auth.AuthorizerOptions{
-		Realm:                 ctlr.Config.HTTP.Auth.Bearer.Realm,
-		Service:               ctlr.Config.HTTP.Auth.Bearer.Service,
-		PublicKeyPath:         ctlr.Config.HTTP.Auth.Bearer.Cert,
-		AccessEntryType:       bearerAuthDefaultAccessEntryType,
-		EmptyDefaultNamespace: true,
-	})
+	certificate, err := loadCertificateFromFile(ctlr.Config.HTTP.Auth.Bearer.Cert)
 	if err != nil {
-		ctlr.Log.Panic().Err(err).Msg("failed to create bearer authorizer")
+		ctlr.Log.Panic().Err(err).Msg("failed to load certificate for bearer authentication")
 	}
 
+	authorizer := NewBearerAuthorizer(
+		ctlr.Config.HTTP.Auth.Bearer.Realm,
+		ctlr.Config.HTTP.Auth.Bearer.Service,
+		certificate.PublicKey,
+	)
+
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
 			if request.Method == http.MethodOptions {
@@ -425,8 +423,6 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
 			}
 
 			acCtrlr := NewAccessController(ctlr.Config)
-			vars := mux.Vars(request)
-			name := vars["name"]
 
 			// we want to bypass auth for mgmt route
 			isMgmtRequested := request.RequestURI == constants.FullMgmt
@@ -439,67 +435,40 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
 				return
 			}
 
-			action := auth.PullAction
-			if m := request.Method; m != http.MethodGet && m != http.MethodHead {
-				action = auth.PushAction
-			}
-
-			var permissions *auth.Permission
+			var requestedAccess *ResourceAction
 
-			// Empty scope should be allowed according to the distribution auth spec
-			// This is only necessary for the bearer auth type
-			if request.RequestURI == "/v2/" && authorizer.Type == auth.BearerAuthAuthorizerType {
-				if header == "" {
-					// first request that clients make (without any header)
-					WWWAuthenticateHeader := fmt.Sprintf("Bearer realm=\"%s\",service=\"%s\",scope=\"\"",
-						authorizer.Realm, authorizer.Service)
+			if request.RequestURI != "/v2/" {
+				// if this is not the base route, the requested repository/action must be authorized
+				vars := mux.Vars(request)
+				name := vars["name"]
 
-					permissions = &auth.Permission{
-						// challenge for the client to use to authenticate to /v2/
-						WWWAuthenticateHeader: WWWAuthenticateHeader,
-						Allowed:               false,
-					}
-				} else {
-					// subsequent requests with token on /v2/
-					bearerTokenMatch := regexp.MustCompile("(?i)bearer (.*)")
-
-					signedString := bearerTokenMatch.ReplaceAllString(header, "$1")
-
-					// If the token is valid, our job is done
-					// Since this is the /v2 base path and we didn't pass a scope to the auth header in the previous step
-					// there is no access check to enforce
-					_, err := authorizer.TokenDecoder.DecodeToken(signedString)
-					if err != nil {
-						ctlr.Log.Error().Err(err).Msg("failed to parse Authorization header")
-						response.Header().Set("Content-Type", "application/json")
-						zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNSUPPORTED))
-
-						return
-					}
+				action := "pull"
+				if m := request.Method; m != http.MethodGet && m != http.MethodHead {
+					action = "push"
+				}
 
-					permissions = &auth.Permission{
-						Allowed: true,
-					}
+				requestedAccess = &ResourceAction{
+					Type:   "repository",
+					Name:   name,
+					Action: action,
 				}
-			} else {
-				var err error
+			}
 
-				// subsequent requests with token on /v2/<resource>/
-				permissions, err = authorizer.Authorize(header, action, name)
-				if err != nil {
-					ctlr.Log.Error().Err(err).Msg("failed to parse Authorization header")
+			err := authorizer.Authorize(header, requestedAccess)
+			if err != nil {
+				var challenge *AuthChallengeError
+				if errors.As(err, &challenge) {
+					ctlr.Log.Debug().Err(challenge).Msg("bearer token authorization failed")
 					response.Header().Set("Content-Type", "application/json")
-					zcommon.WriteJSON(response, http.StatusInternalServerError, apiErr.NewError(apiErr.UNSUPPORTED))
+					response.Header().Set("WWW-Authenticate", challenge.Header())
+					zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
 
 					return
 				}
-			}
 
-			if !permissions.Allowed {
+				ctlr.Log.Error().Err(err).Msg("failed to parse Authorization header")
 				response.Header().Set("Content-Type", "application/json")
-				response.Header().Set("WWW-Authenticate", permissions.WWWAuthenticateHeader)
-
-				zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
+				zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNSUPPORTED))
 
 				return
 			}
@@ -932,3 +901,22 @@ func GenerateAPIKey(uuidGenerator guuid.Generator, log log.Logger,
 
 	return apiKey, apiKeyID.String(), err
 }
+
+func loadCertificateFromFile(path string) (*x509.Certificate, error) {
+	rawCert, err := os.ReadFile(path)
+	if err != nil {
+		return nil, fmt.Errorf("%w: %w, path %s", zerr.ErrCouldNotLoadCertificate, err, path)
+	}
+
+	block, _ := pem.Decode(rawCert)
+	if block == nil {
+		return nil, fmt.Errorf("%w: no valid PEM data found", zerr.ErrCouldNotLoadCertificate)
+	}
+
+	cert, err := x509.ParseCertificate(block.Bytes)
+	if err != nil {
+		return nil, fmt.Errorf("%w: %w", zerr.ErrCouldNotLoadCertificate, err)
+	}
+
+	return cert, nil
+}
diff --git a/pkg/api/bearer.go b/pkg/api/bearer.go
new file mode 100644
index 00000000..861654c0
--- /dev/null
+++ b/pkg/api/bearer.go
@@ -0,0 +1,136 @@
+package api
+
+import (
+	"crypto"
+	"fmt"
+	"regexp"
+	"slices"
+
+	"github.com/golang-jwt/jwt/v5"
+
+	zerr "zotregistry.dev/zot/errors"
+)
+
+var bearerTokenMatch = regexp.MustCompile("(?i)bearer (.*)")
+
+// ResourceAccess is a single entry in the private 'access' claim specified by the distribution token authentication
+// specification.
+type ResourceAccess struct {
+	Type    string   `json:"type"`
+	Name    string   `json:"name"`
+	Actions []string `json:"actions"`
+}
+
+type ResourceAction struct {
+	Type   string `json:"type"`
+	Name   string `json:"name"`
+	Action string `json:"action"`
+}
+
+// ClaimsWithAccess is a claim set containing the private 'access' claim specified by the distribution token
+// authentication specification, in addition to the standard registered claims.
+// https://distribution.github.io/distribution/spec/auth/jwt/
+type ClaimsWithAccess struct {
+	Access []ResourceAccess `json:"access"`
+	jwt.RegisteredClaims
+}
+
+type AuthChallengeError struct {
+	err            error
+	realm          string
+	service        string
+	resourceAction *ResourceAction
+}
+
+func (c AuthChallengeError) Error() string {
+	return c.err.Error()
+}
+
+// Header constructs an appropriate value for the WWW-Authenticate header to be returned to the client.
+func (c AuthChallengeError) Header() string {
+	if c.resourceAction == nil {
+		// no access was requested, so return an empty scope
+		return fmt.Sprintf("Bearer realm=\"%s\",service=\"%s\",scope=\"\"",
+			c.realm, c.service)
+	}
+
+	return fmt.Sprintf("Bearer realm=\"%s\",service=\"%s\",scope=\"%s:%s:%s\"",
+		c.realm, c.service, c.resourceAction.Type, c.resourceAction.Name, c.resourceAction.Action)
+}
+
+type BearerAuthorizer struct {
+	realm   string
+	service string
+	key     crypto.PublicKey
+}
+
+func NewBearerAuthorizer(realm string, service string, key crypto.PublicKey) BearerAuthorizer {
+	return BearerAuthorizer{
+		realm:   realm,
+		service: service,
+		key:     key,
+	}
+}
+
+// Authorize verifies whether the bearer token in the given Authorization header is valid, and whether it has sufficient
+// scope for the requested resource action. If an authorization error occurs (e.g. no token is given or the token has
+// insufficient scope), an AuthChallengeError is returned as the error.
+func (a *BearerAuthorizer) Authorize(header string, requested *ResourceAction) error {
+	challenge := &AuthChallengeError{
+		realm:          a.realm,
+		service:        a.service,
+		resourceAction: requested,
+	}
+
+	if header == "" {
+		// if no bearer token is set in the authorization header, return the authentication challenge
+		challenge.err = zerr.ErrNoBearerToken
+
+		return challenge
+	}
+
+	signedString := bearerTokenMatch.ReplaceAllString(header, "$1")
+
+	token, err := jwt.ParseWithClaims(signedString, &ClaimsWithAccess{}, func(token *jwt.Token) (interface{}, error) {
+		return a.key, nil
+	}, jwt.WithValidMethods(a.allowedSigningAlgorithms()), jwt.WithIssuedAt())
+	if err != nil {
+		return fmt.Errorf("%w: %w", zerr.ErrInvalidBearerToken, err)
+	}
+
+	if requested == nil {
+		// the token is valid and no access is requested, so we do not have to validate the access claim
+		return nil
+	}
+
+	claims, ok := token.Claims.(*ClaimsWithAccess)
+	if !ok {
+		return fmt.Errorf("%w: invalid claims type", zerr.ErrInvalidBearerToken)
+	}
+
+	// check whether the requested access is allowed by the scope of the token
+	for _, allowed := range claims.Access {
+		if allowed.Type != requested.Type {
+			continue
+		}
+
+		if allowed.Name != requested.Name {
+			continue
+		}
+
+		if !slices.Contains(allowed.Actions, requested.Action) {
+			continue
+		}
+
+		// requested action is allowed, so don't return an error
+		return nil
+	}
+
+	challenge.err = zerr.ErrInsufficientScope
+
+	return challenge
+}
+
+func (a *BearerAuthorizer) allowedSigningAlgorithms() []string {
+	return []string{"EdDSA", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512"}
+}



Test patch:
diff --git a/pkg/api/bearer_test.go b/pkg/api/bearer_test.go
new file mode 100644
index 00000000..69d62d9e
--- /dev/null
+++ b/pkg/api/bearer_test.go
@@ -0,0 +1,121 @@
+package api_test
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"testing"
+	"time"
+
+	"github.com/golang-jwt/jwt/v5"
+	. "github.com/smartystreets/goconvey/convey"
+
+	zerr "zotregistry.dev/zot/errors"
+	"zotregistry.dev/zot/pkg/api"
+)
+
+func TestBearerAuthorizer(t *testing.T) {
+	Convey("Test bearer token authorization", t, func() {
+		signingMethod := jwt.SigningMethodRS256
+
+		privKey, err := rsa.GenerateKey(rand.Reader, 2048)
+		if err != nil {
+			panic(err)
+		}
+
+		pubKey := privKey.Public()
+
+		authorizer := api.NewBearerAuthorizer("realm", "service", pubKey)
+
+		Convey("Empty authorization header given", func() {
+			err := authorizer.Authorize("", nil)
+			So(err, ShouldBeError, zerr.ErrNoBearerToken)
+		})
+
+		Convey("Valid token", func() {
+			access := []api.ResourceAccess{
+				{
+					Name:    "authorized-repository",
+					Type:    "repository",
+					Actions: []string{"pull"},
+				},
+			}
+
+			now := time.Now()
+			claims := api.ClaimsWithAccess{
+				Access: access,
+				RegisteredClaims: jwt.RegisteredClaims{
+					ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 1)),
+					IssuedAt:  jwt.NewNumericDate(now),
+					Issuer:    "Zot",
+					Audience:  []string{"Zot Registry"},
+				},
+			}
+
+			token, err := jwt.NewWithClaims(signingMethod, claims).SignedString(privKey)
+			if err != nil {
+				panic(err)
+			}
+
+			authHeader := "Bearer " + token
+
+			Convey("Unauthorized type", func() {
+				requested := &api.ResourceAction{
+					Type:   "registry",
+					Name:   "catalog",
+					Action: "*",
+				}
+
+				err := authorizer.Authorize(authHeader, requested)
+				So(err, ShouldHaveSameTypeAs, &api.AuthChallengeError{})
+				So(err, ShouldBeError, zerr.ErrInsufficientScope)
+			})
+
+			Convey("Unauthorized name", func() {
+				requested := &api.ResourceAction{
+					Type:   "repository",
+					Name:   "unauthorized-repository",
+					Action: "pull",
+				}
+
+				err := authorizer.Authorize(authHeader, requested)
+				So(err, ShouldHaveSameTypeAs, &api.AuthChallengeError{})
+				So(err, ShouldBeError, zerr.ErrInsufficientScope)
+			})
+
+			Convey("Unauthorized action", func() {
+				requested := &api.ResourceAction{
+					Type:   "repository",
+					Name:   "authorized-repository",
+					Action: "push",
+				}
+
+				err := authorizer.Authorize(authHeader, requested)
+				So(err, ShouldHaveSameTypeAs, &api.AuthChallengeError{})
+				So(err, ShouldBeError, zerr.ErrInsufficientScope)
+			})
+
+			Convey("Successful authorization with requested access", func() {
+				requested := &api.ResourceAction{
+					Type:   "repository",
+					Name:   "authorized-repository",
+					Action: "pull",
+				}
+
+				err := authorizer.Authorize(authHeader, requested)
+				So(err, ShouldBeNil)
+			})
+
+			Convey("Successful authorization without requested access", func() {
+				err := authorizer.Authorize(authHeader, nil)
+				So(err, ShouldBeNil)
+			})
+		})
+
+		Convey("Invalid token", func() {
+			authHeader := "invalid"
+
+			err := authorizer.Authorize(authHeader, nil)
+			So(err, ShouldWrap, zerr.ErrInvalidBearerToken)
+		})
+	})
+}
diff --git a/pkg/api/controller_test.go b/pkg/api/controller_test.go
index 81063ef2..a6305359 100644
--- a/pkg/api/controller_test.go
+++ b/pkg/api/controller_test.go
@@ -78,6 +78,10 @@ const (
 	ServerCert             = "../../test/data/server.cert"
 	ServerKey              = "../../test/data/server.key"
 	CACert                 = "../../test/data/ca.crt"
+	ServerCertECDSA        = "../../test/data/server-ecdsa.cert"
+	ServerKeyECDSA         = "../../test/data/server-ecdsa.key"
+	ServerCertED25519      = "../../test/data/server-ed25519.cert"
+	ServerKeyED25519       = "../../test/data/server-ed25519.key"
 	UnauthorizedNamespace  = "fortknox/notallowed"
 	AuthorizationNamespace = "authz/image"
 	LDAPAddress            = "127.0.0.1"
@@ -2310,6 +2314,11 @@ func TestAuthnErrors(t *testing.T) {
 		err := os.WriteFile(tmpFile, []byte("test"), 0o000)
 		So(err, ShouldBeNil)
 
+		defer func() {
+			err := os.Chmod(tmpFile, 0o644)
+			So(err, ShouldBeNil)
+		}()
+
 		conf.HTTP.Auth.LDAP = (&config.LDAPConfig{
 			Insecure:      true,
 			Address:       LDAPAddress,
@@ -2324,9 +2333,6 @@ func TestAuthnErrors(t *testing.T) {
 		So(func() {
 			api.AuthHandler(ctlr)
 		}, ShouldPanic)
-
-		err = os.Chmod(tmpFile, 0o644)
-		So(err, ShouldBeNil)
 	})
 
 	Convey("ldap CA certs is empty", t, func() {
@@ -2387,6 +2393,11 @@ func TestAuthnErrors(t *testing.T) {
 		err := os.WriteFile(tmpFile, []byte("test"), 0o000)
 		So(err, ShouldBeNil)
 
+		defer func() {
+			err := os.Chmod(tmpFile, 0o644)
+			So(err, ShouldBeNil)
+		}()
+
 		conf.HTTP.Auth.HTPasswd = config.AuthHTPasswd{
 			Path: tmpFile,
 		}
@@ -2396,9 +2407,63 @@ func TestAuthnErrors(t *testing.T) {
 		So(func() {
 			api.AuthHandler(ctlr)
 		}, ShouldPanic)
+	})
+
+	Convey("Bearer auth invalid PEM data", t, func() {
+		port := test.GetFreePort()
+		conf := config.New()
+		conf.HTTP.Port = port
+		tmpDir := t.TempDir()
+		tmpFile := path.Join(tmpDir, "invalid-server.cert")
+
+		err := os.WriteFile(tmpFile, []byte("invalid"), 0o000)
+		So(err, ShouldBeNil)
+
+		defer func() {
+			err := os.Chmod(tmpFile, 0o644)
+			So(err, ShouldBeNil)
+		}()
+
+		conf.HTTP.Auth.Bearer = &config.BearerConfig{
+			Realm:   "realm",
+			Service: "service",
+			Cert:    tmpFile,
+		}
+
+		ctlr := makeController(conf, t.TempDir())
+
+		So(func() {
+			api.AuthHandler(ctlr)
+		}, ShouldPanic)
+	})
+
+	Convey("Bearer auth invalid certificate", t, func() {
+		port := test.GetFreePort()
+		conf := config.New()
+		conf.HTTP.Port = port
+		tmpDir := t.TempDir()
+		tmpFile := path.Join(tmpDir, "invalid-server.cert")
 
-		err = os.Chmod(tmpFile, 0o644)
+		// 'invalid' encoded as PEM
+		err := os.WriteFile(tmpFile, []byte("-----BEGIN CERTIFICATE-----\naW52YWxpZA==\n-----END CERTIFICATE-----"), 0o000)
 		So(err, ShouldBeNil)
+
+		defer func() {
+			err := os.Chmod(tmpFile, 0o644)
+			So(err, ShouldBeNil)
+		}()
+
+		conf.HTTP.Auth.Bearer = &config.BearerConfig{
+			Realm:   "realm",
+			Service: "service",
+			Cert:    tmpFile,
+		}
+
+		ctlr := makeController(conf, t.TempDir())
+
+		So(func() {
+			api.AuthHandler(ctlr)
+		}, ShouldPanic)
 	})
 
 	Convey("NewRelyingPartyGithub fail", t, func() {
@@ -2411,6 +2476,11 @@ func TestAuthnErrors(t *testing.T) {
 		err := os.WriteFile(tmpFile, []byte("test"), 0o000)
 		So(err, ShouldBeNil)
 
+		defer func() {
+			err := os.Chmod(tmpFile, 0o644)
+			So(err, ShouldBeNil)
+		}()
+
 		conf.HTTP.Auth.HTPasswd = config.AuthHTPasswd{
 			Path: tmpFile,
 		}
@@ -2418,9 +2488,6 @@ func TestAuthnErrors(t *testing.T) {
 		So(func() {
 			api.NewRelyingPartyGithub(conf, "prov", nil, nil, log.NewLogger("debug", ""))
 		}, ShouldPanic)
-
-		err = os.Chmod(tmpFile, 0o644)
-		So(err, ShouldBeNil)
 	})
 }
 
@@ -3846,190 +3913,294 @@ func TestLDAPClient(t *testing.T) {
 	})
 }
 
+func TestBearerAuthMultipleAlgorithms(t *testing.T) {
+	testCases := []struct {
+		name string
+		key  string
+		cert string
+		alg  string
+	}{
+		{
+			"RSA signing key",
+			ServerKey,
+			ServerCert,
+			"RS256",
+		},
+		{
+			"ECDSA signing key",
+			ServerKeyECDSA,
+			ServerCertECDSA,
+			"ES256",
+		},
+		{
+			"ED25519 signing key",
+			ServerKeyED25519,
+			ServerCertED25519,
+			"EdDSA",
+		},
+	}
+
+	for _, testCase := range testCases {
+		Convey("Make a new controller with "+testCase.name, t, func() {
+			authTestServer := authutils.MakeAuthTestServer(testCase.key, testCase.alg, UnauthorizedNamespace)
+			defer authTestServer.Close()
+
+			port := test.GetFreePort()
+			baseURL := test.GetBaseURL(port)
+
+			conf := config.New()
+			conf.HTTP.Port = port
+
+			aurl, err := url.Parse(authTestServer.URL)
+			So(err, ShouldBeNil)
+
+			conf.HTTP.Auth = &config.AuthConfig{
+				Bearer: &config.BearerConfig{
+					Cert:    testCase.cert,
+					Realm:   authTestServer.URL + "/auth/token",
+					Service: aurl.Host,
+				},
+			}
+			ctlr := makeController(conf, t.TempDir())
+
+			cm := test.NewControllerManager(ctlr)
+			cm.StartAndWait(port)
+
+			defer cm.StopServer()
+
+			resp, err := resty.R().Get(baseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+
+			authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+
+			var goodToken authutils.AccessTokenResponse
+
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
+
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(baseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+		})
+	}
+}
+
 func TestBearerAuth(t *testing.T) {
-	Convey("Make a new controller", t, func() {
-		authTestServer := authutils.MakeAuthTestServer(ServerKey, UnauthorizedNamespace)
-		defer authTestServer.Close()
+	testCases := []struct {
+		name                    string
+		useLegacyAuthTestServer bool
+	}{
+		{
+			name:                    "new authentication test server",
+			useLegacyAuthTestServer: false,
+		},
+		{
+			name:                    "legacy authentication test server",
+			useLegacyAuthTestServer: true,
+		},
+	}
 
-		port := test.GetFreePort()
-		baseURL := test.GetBaseURL(port)
+	for _, testCase := range testCases {
+		Convey("Make a new controller with "+testCase.name, t, func() {
+			var authTestServer *httptest.Server
+			if testCase.useLegacyAuthTestServer {
+				authTestServer = authutils.MakeAuthTestServerLegacy(ServerKey, UnauthorizedNamespace)
+			} else {
+				authTestServer = authutils.MakeAuthTestServer(ServerKey, "RS256", UnauthorizedNamespace)
+			}
+			defer authTestServer.Close()
 
-		conf := config.New()
-		conf.HTTP.Port = port
+			port := test.GetFreePort()
+			baseURL := test.GetBaseURL(port)
 
-		aurl, err := url.Parse(authTestServer.URL)
-		So(err, ShouldBeNil)
+			conf := config.New()
+			conf.HTTP.Port = port
 
-		conf.HTTP.Auth = &config.AuthConfig{
-			Bearer: &config.BearerConfig{
-				Cert:    ServerCert,
-				Realm:   authTestServer.URL + "/auth/token",
-				Service: aurl.Host,
-			},
-		}
-		ctlr := makeController(conf, t.TempDir())
+			aurl, err := url.Parse(authTestServer.URL)
+			So(err, ShouldBeNil)
 
-		cm := test.NewControllerManager(ctlr)
-		cm.StartAndWait(port)
+			conf.HTTP.Auth = &config.AuthConfig{
+				Bearer: &config.BearerConfig{
+					Cert:    ServerCert,
+					Realm:   authTestServer.URL + "/auth/token",
+					Service: aurl.Host,
+				},
+			}
+			ctlr := makeController(conf, t.TempDir())
 
-		defer cm.StopServer()
+			cm := test.NewControllerManager(ctlr)
+			cm.StartAndWait(port)
 
-		blob := []byte("hello, blob!")
-		digest := godigest.FromBytes(blob).String()
+			defer cm.StopServer()
 
-		resp, err := resty.R().Get(baseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			blob := []byte("hello, blob!")
+			digest := godigest.FromBytes(blob).String()
 
-		authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			resp, err := resty.R().Get(baseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		var goodToken authutils.AccessTokenResponse
+			authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			var goodToken authutils.AccessTokenResponse
 
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Get(baseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		// trigger decode error
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+"invalidToken").
-			Get(baseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(baseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		resp, err = resty.R().SetHeader("Authorization",
-			"Bearer "+goodToken.AccessToken).Options(baseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
+			// trigger decode error
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+"invalidToken").
+				Get(baseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		s1, seed1 := test.GenerateRandomName()
-		s2, seed2 := test.GenerateRandomName()
-		repoName := s1 + "/" + s2
+			resp, err = resty.R().SetHeader("Authorization",
+				"Bearer "+goodToken.AccessToken).Options(baseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
 
-		ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName")
+			s1, seed1 := test.GenerateRandomName()
+			s2, seed2 := test.GenerateRandomName()
+			repoName := s1 + "/" + s2
 
-		resp, err = resty.R().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName")
 
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			resp, err = resty.R().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
-		loc := resp.Header().Get("Location")
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		resp, err = resty.R().
-			SetHeader("Content-Length", strconv.Itoa(len(blob))).
-			SetHeader("Content-Type", "application/octet-stream").
-			SetQueryParam("digest", digest).
-			SetBody(blob).
-			Put(baseURL + loc)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
+			loc := resp.Header().Get("Location")
 
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			resp, err = resty.R().
+				SetHeader("Content-Length", strconv.Itoa(len(blob))).
+				SetHeader("Content-Type", "application/octet-stream").
+				SetQueryParam("digest", digest).
+				SetBody(blob).
+				Put(baseURL + loc)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		resp, err = resty.R().
-			SetHeader("Content-Length", strconv.Itoa(len(blob))).
-			SetHeader("Content-Type", "application/octet-stream").
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			SetQueryParam("digest", digest).
-			SetBody(blob).
-			Put(baseURL + loc)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Get(baseURL + "/v2/" + repoName + "/tags/list")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			resp, err = resty.R().
+				SetHeader("Content-Length", strconv.Itoa(len(blob))).
+				SetHeader("Content-Type", "application/octet-stream").
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				SetQueryParam("digest", digest).
+				SetBody(blob).
+				Put(baseURL + loc)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
 
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(baseURL + "/v2/" + repoName + "/tags/list")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Get(baseURL + "/v2/" + repoName + "/tags/list")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		resp, err = resty.R().
-			Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(baseURL + "/v2/" + repoName + "/tags/list")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			resp, err = resty.R().
+				Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		var badToken authutils.AccessTokenResponse
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		err = json.Unmarshal(resp.Body(), &badToken)
-		So(err, ShouldBeNil)
+			var badToken authutils.AccessTokenResponse
 
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+badToken.AccessToken).
-			Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
-	})
+			err = json.Unmarshal(resp.Body(), &badToken)
+			So(err, ShouldBeNil)
+
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+badToken.AccessToken).
+				Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+		})
+	}
 }
 
 func TestBearerAuthWrongAuthorizer(t *testing.T) {
@@ -4054,183 +4225,204 @@ func TestBearerAuthWrongAuthorizer(t *testing.T) {
 }
 
 func TestBearerAuthWithAllowReadAccess(t *testing.T) {
-	Convey("Make a new controller", t, func() {
-		authTestServer := authutils.MakeAuthTestServer(ServerKey, UnauthorizedNamespace)
-		defer authTestServer.Close()
+	testCases := []struct {
+		name                    string
+		useLegacyAuthTestServer bool
+	}{
+		{
+			name:                    "new authentication test server",
+			useLegacyAuthTestServer: false,
+		},
+		{
+			name:                    "legacy authentication test server",
+			useLegacyAuthTestServer: true,
+		},
+	}
 
-		port := test.GetFreePort()
-		baseURL := test.GetBaseURL(port)
+	for _, testCase := range testCases {
+		Convey("Make a new controller with"+testCase.name, t, func() {
+			var authTestServer *httptest.Server
+			if testCase.useLegacyAuthTestServer {
+				authTestServer = authutils.MakeAuthTestServerLegacy(ServerKey, UnauthorizedNamespace)
+			} else {
+				authTestServer = authutils.MakeAuthTestServer(ServerKey, "RS256", UnauthorizedNamespace)
+			}
+			defer authTestServer.Close()
 
-		conf := config.New()
-		conf.HTTP.Port = port
+			port := test.GetFreePort()
+			baseURL := test.GetBaseURL(port)
 
-		aurl, err := url.Parse(authTestServer.URL)
-		So(err, ShouldBeNil)
+			conf := config.New()
+			conf.HTTP.Port = port
 
-		conf.HTTP.Auth = &config.AuthConfig{
-			Bearer: &config.BearerConfig{
-				Cert:    ServerCert,
-				Realm:   authTestServer.URL + "/auth/token",
-				Service: aurl.Host,
-			},
-		}
-		ctlr := makeController(conf, t.TempDir())
+			aurl, err := url.Parse(authTestServer.URL)
+			So(err, ShouldBeNil)
 
-		conf.HTTP.AccessControl = &config.AccessControlConfig{
-			Repositories: config.Repositories{
-				test.AuthorizationAllRepos: config.PolicyGroup{
-					AnonymousPolicy: []string{"read"},
+			conf.HTTP.Auth = &config.AuthConfig{
+				Bearer: &config.BearerConfig{
+					Cert:    ServerCert,
+					Realm:   authTestServer.URL + "/auth/token",
+					Service: aurl.Host,
 				},
-			},
-		}
+			}
+			ctlr := makeController(conf, t.TempDir())
 
-		cm := test.NewControllerManager(ctlr)
-		cm.StartAndWait(port)
+			conf.HTTP.AccessControl = &config.AccessControlConfig{
+				Repositories: config.Repositories{
+					test.AuthorizationAllRepos: config.PolicyGroup{
+						AnonymousPolicy: []string{"read"},
+					},
+				},
+			}
 
-		defer cm.StopServer()
+			cm := test.NewControllerManager(ctlr)
+			cm.StartAndWait(port)
 
-		blob := []byte("hello, blob!")
-		digest := godigest.FromBytes(blob).String()
+			defer cm.StopServer()
 
-		resp, err := resty.R().Get(baseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			blob := []byte("hello, blob!")
+			digest := godigest.FromBytes(blob).String()
 
-		authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			resp, err := resty.R().Get(baseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+
+			authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		var goodToken authutils.AccessTokenResponse
+			var goodToken authutils.AccessTokenResponse
 
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Get(baseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(baseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		s1, seed1 := test.GenerateRandomName()
-		s2, seed2 := test.GenerateRandomName()
-		repoName := s1 + "/" + s2
+			s1, seed1 := test.GenerateRandomName()
+			s2, seed2 := test.GenerateRandomName()
+			repoName := s1 + "/" + s2
 
-		ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName")
+			ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName")
 
-		resp, err = resty.R().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			resp, err = resty.R().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
-		loc := resp.Header().Get("Location")
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
+			loc := resp.Header().Get("Location")
 
-		resp, err = resty.R().
-			SetHeader("Content-Length", strconv.Itoa(len(blob))).
-			SetHeader("Content-Type", "application/octet-stream").
-			SetQueryParam("digest", digest).
-			SetBody(blob).
-			Put(baseURL + loc)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			resp, err = resty.R().
+				SetHeader("Content-Length", strconv.Itoa(len(blob))).
+				SetHeader("Content-Type", "application/octet-stream").
+				SetQueryParam("digest", digest).
+				SetBody(blob).
+				Put(baseURL + loc)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		resp, err = resty.R().
-			SetHeader("Content-Length", strconv.Itoa(len(blob))).
-			SetHeader("Content-Type", "application/octet-stream").
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			SetQueryParam("digest", digest).
-			SetBody(blob).
-			Put(baseURL + loc)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
+			resp, err = resty.R().
+				SetHeader("Content-Length", strconv.Itoa(len(blob))).
+				SetHeader("Content-Type", "application/octet-stream").
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				SetQueryParam("digest", digest).
+				SetBody(blob).
+				Put(baseURL + loc)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
 
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Get(baseURL + "/v2/" + repoName + "/tags/list")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(baseURL + "/v2/" + repoName + "/tags/list")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Get(baseURL + "/v2/" + repoName + "/tags/list")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(baseURL + "/v2/" + repoName + "/tags/list")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		resp, err = resty.R().
-			Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			resp, err = resty.R().
+				Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		var badToken authutils.AccessTokenResponse
-		err = json.Unmarshal(resp.Body(), &badToken)
-		So(err, ShouldBeNil)
+			var badToken authutils.AccessTokenResponse
+			err = json.Unmarshal(resp.Body(), &badToken)
+			So(err, ShouldBeNil)
 
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+badToken.AccessToken).
-			Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
-	})
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+badToken.AccessToken).
+				Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+		})
+	}
 }
 
 func TestNewRelyingPartyOIDC(t *testing.T) {
diff --git a/pkg/extensions/extensions_test.go b/pkg/extensions/extensions_test.go
index 66086523..d1fed0c7 100644
--- a/pkg/extensions/extensions_test.go
+++ b/pkg/extensions/extensions_test.go
@@ -6,6 +6,7 @@ package extensions_test
 import (
 	"encoding/json"
 	"net/http"
+	"net/http/httptest"
 	"net/url"
 	"os"
 	"testing"
@@ -868,160 +869,181 @@ func TestMgmtExtension(t *testing.T) {
 }
 
 func TestMgmtWithBearer(t *testing.T) {
-	Convey("Make a new controller", t, func() {
-		authorizedNamespace := "allowedrepo"
-		unauthorizedNamespace := "notallowedrepo"
-
-		authTestServer := authutils.MakeAuthTestServer(ServerKey, unauthorizedNamespace)
-		defer authTestServer.Close()
-
-		port := test.GetFreePort()
-		baseURL := test.GetBaseURL(port)
-
-		conf := config.New()
-		conf.HTTP.Port = port
-
-		aurl, err := url.Parse(authTestServer.URL)
-		So(err, ShouldBeNil)
-
-		conf.HTTP.Auth = &config.AuthConfig{
-			Bearer: &config.BearerConfig{
-				Cert:    ServerCert,
-				Realm:   authTestServer.URL + "/auth/token",
-				Service: aurl.Host,
-			},
-		}
-
-		defaultValue := true
-
-		conf.Extensions = &extconf.ExtensionConfig{}
-		conf.Extensions.Search = &extconf.SearchConfig{}
-		conf.Extensions.Search.Enable = &defaultValue
-		conf.Extensions.Search.CVE = nil
-		conf.Extensions.UI = &extconf.UIConfig{}
-		conf.Extensions.UI.Enable = &defaultValue
-
-		conf.Storage.RootDirectory = t.TempDir()
-
-		ctlr := api.NewController(conf)
-
-		cm := test.NewControllerManager(ctlr)
-		cm.StartAndWait(port)
-		defer cm.StopServer()
-
-		resp, err := resty.R().Get(baseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
-
-		authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
-
-		var goodToken authutils.AccessTokenResponse
-
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
-
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Get(baseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
-
-		resp, err = resty.R().SetHeader("Authorization",
-			"Bearer "+goodToken.AccessToken).Options(baseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
-
-		resp, err = resty.R().Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
-
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
-
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
-
-		resp, err = resty.R().
-			Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
-
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+	testCases := []struct {
+		name                    string
+		useLegacyAuthTestServer bool
+	}{
+		{
+			name:                    "new authentication test server",
+			useLegacyAuthTestServer: false,
+		},
+		{
+			name:                    "legacy authentication test server",
+			useLegacyAuthTestServer: true,
+		},
+	}
 
-		var badToken authutils.AccessTokenResponse
+	for _, testCase := range testCases {
+		Convey("Make a new controller with "+testCase.name, t, func() {
+			authorizedNamespace := "allowedrepo"
+			unauthorizedNamespace := "notallowedrepo"
 
-		err = json.Unmarshal(resp.Body(), &badToken)
-		So(err, ShouldBeNil)
+			var authTestServer *httptest.Server
+			if testCase.useLegacyAuthTestServer {
+				authTestServer = authutils.MakeAuthTestServerLegacy(ServerKey, unauthorizedNamespace)
+			} else {
+				authTestServer = authutils.MakeAuthTestServer(ServerKey, "RS256", unauthorizedNamespace)
+			}
+			defer authTestServer.Close()
 
-		resp, err = resty.R().
-			SetHeader("Authorization", "Bearer "+badToken.AccessToken).
-			Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			port := test.GetFreePort()
+			baseURL := test.GetBaseURL(port)
 
-		// test mgmt route
-		resp, err = resty.R().Get(baseURL + constants.FullMgmt)
-		So(err, ShouldBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			conf := config.New()
+			conf.HTTP.Port = port
 
-		mgmtResp := extensions.StrippedConfig{}
-		err = json.Unmarshal(resp.Body(), &mgmtResp)
-		So(err, ShouldBeNil)
-		So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion)
-		So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
-		So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm)
-		So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service)
-		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil)
-		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
-		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
+			aurl, err := url.Parse(authTestServer.URL)
+			So(err, ShouldBeNil)
 
-		resp, err = resty.R().SetBasicAuth("", "").Get(baseURL + constants.FullMgmt)
-		So(err, ShouldBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			conf.HTTP.Auth = &config.AuthConfig{
+				Bearer: &config.BearerConfig{
+					Cert:    ServerCert,
+					Realm:   authTestServer.URL + "/auth/token",
+					Service: aurl.Host,
+				},
+			}
 
-		mgmtResp = extensions.StrippedConfig{}
-		err = json.Unmarshal(resp.Body(), &mgmtResp)
-		So(err, ShouldBeNil)
-		So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion)
-		So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
-		So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm)
-		So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service)
-		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil)
-		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
-		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
-	})
+			defaultValue := true
+
+			conf.Extensions = &extconf.ExtensionConfig{}
+			conf.Extensions.Search = &extconf.SearchConfig{}
+			conf.Extensions.Search.Enable = &defaultValue
+			conf.Extensions.Search.CVE = nil
+			conf.Extensions.UI = &extconf.UIConfig{}
+			conf.Extensions.UI.Enable = &defaultValue
+
+			conf.Storage.RootDirectory = t.TempDir()
+
+			ctlr := api.NewController(conf)
+
+			cm := test.NewControllerManager(ctlr)
+			cm.StartAndWait(port)
+			defer cm.StopServer()
+
+			resp, err := resty.R().Get(baseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+
+			authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+
+			var goodToken authutils.AccessTokenResponse
+
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
+
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(baseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+
+			resp, err = resty.R().SetHeader("Authorization",
+				"Bearer "+goodToken.AccessToken).Options(baseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
+
+			resp, err = resty.R().Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
+
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
+
+			resp, err = resty.R().
+				Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+
+			var badToken authutils.AccessTokenResponse
+
+			err = json.Unmarshal(resp.Body(), &badToken)
+			So(err, ShouldBeNil)
+
+			resp, err = resty.R().
+				SetHeader("Authorization", "Bearer "+badToken.AccessToken).
+				Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+
+			// test mgmt route
+			resp, err = resty.R().Get(baseURL + constants.FullMgmt)
+			So(err, ShouldBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+
+			mgmtResp := extensions.StrippedConfig{}
+			err = json.Unmarshal(resp.Body(), &mgmtResp)
+			So(err, ShouldBeNil)
+			So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion)
+			So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
+			So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm)
+			So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service)
+			So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil)
+			So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
+			So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
+
+			resp, err = resty.R().SetBasicAuth("", "").Get(baseURL + constants.FullMgmt)
+			So(err, ShouldBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+
+			mgmtResp = extensions.StrippedConfig{}
+			err = json.Unmarshal(resp.Body(), &mgmtResp)
+			So(err, ShouldBeNil)
+			So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion)
+			So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
+			So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm)
+			So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service)
+			So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil)
+			So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
+			So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
+		})
+	}
 }
 
 func TestAllowedMethodsHeaderMgmt(t *testing.T) {
diff --git a/pkg/extensions/sync/sync_test.go b/pkg/extensions/sync/sync_test.go
index 53abcae1..c7f2b423 100644
--- a/pkg/extensions/sync/sync_test.go
+++ b/pkg/extensions/sync/sync_test.go
@@ -11,6 +11,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"net/http/httptest"
 	"net/url"
 	"os"
 	"path"
@@ -2553,293 +2554,319 @@ func TestTLS(t *testing.T) {
 }
 
 func TestBearerAuth(t *testing.T) {
-	Convey("Verify periodically sync bearer auth", t, func() {
-		updateDuration, _ := time.ParseDuration("1h")
-		// a repo for which clients do not have access, sync shouldn't be able to sync it
-		unauthorizedNamespace := testCveImage
+	testCases := []struct {
+		name                    string
+		useLegacyAuthTestServer bool
+	}{
+		{
+			name:                    "new authentication test server",
+			useLegacyAuthTestServer: false,
+		},
+		{
+			name:                    "legacy authentication test server",
+			useLegacyAuthTestServer: true,
+		},
+	}
 
-		authTestServer := authutils.MakeAuthTestServer(ServerKey, unauthorizedNamespace)
-		defer authTestServer.Close()
+	for _, testCase := range testCases {
+		Convey("Verify periodically sync bearer auth with "+testCase.name, t, func() {
+			updateDuration, _ := time.ParseDuration("1h")
+			// a repo for which clients do not have access, sync shouldn't be able to sync it
+			unauthorizedNamespace := testCveImage
+
+			var authTestServer *httptest.Server
+			if testCase.useLegacyAuthTestServer {
+				authTestServer = authutils.MakeAuthTestServerLegacy(ServerKey, unauthorizedNamespace)
+			} else {
+				authTestServer = authutils.MakeAuthTestServer(ServerKey, "RS256", unauthorizedNamespace)
+			}
+			defer authTestServer.Close()
 
-		sctlr, srcBaseURL, _, _, srcClient := makeUpstreamServer(t, false, false)
+			sctlr, srcBaseURL, _, _, srcClient := makeUpstreamServer(t, false, false)
 
-		aurl, err := url.Parse(authTestServer.URL)
-		So(err, ShouldBeNil)
+			aurl, err := url.Parse(authTestServer.URL)
+			So(err, ShouldBeNil)
 
-		sctlr.Config.HTTP.Auth = &config.AuthConfig{
-			Bearer: &config.BearerConfig{
-				Cert:    ServerCert,
-				Realm:   authTestServer.URL + "/auth/token",
-				Service: aurl.Host,
-			},
-		}
+			sctlr.Config.HTTP.Auth = &config.AuthConfig{
+				Bearer: &config.BearerConfig{
+					Cert:    ServerCert,
+					Realm:   authTestServer.URL + "/auth/token",
+					Service: aurl.Host,
+				},
+			}
 
-		scm := test.NewControllerManager(sctlr)
-		scm.StartAndWait(sctlr.Config.HTTP.Port)
+			scm := test.NewControllerManager(sctlr)
+			scm.StartAndWait(sctlr.Config.HTTP.Port)
 
-		defer scm.StopServer()
+			defer scm.StopServer()
 
-		registryName := sync.StripRegistryTransport(srcBaseURL)
-		credentialsFile := makeCredentialsFile(fmt.Sprintf(`{"%s":{"username": "%s", "password": "%s"}}`,
-			registryName, username, password))
+			registryName := sync.StripRegistryTransport(srcBaseURL)
+			credentialsFile := makeCredentialsFile(fmt.Sprintf(`{"%s":{"username": "%s", "password": "%s"}}`,
+				registryName, username, password))
 
-		var tlsVerify bool
+			var tlsVerify bool
 
-		syncRegistryConfig := syncconf.RegistryConfig{
-			Content: []syncconf.Content{
-				{
-					Prefix: "**", // sync everything
+			syncRegistryConfig := syncconf.RegistryConfig{
+				Content: []syncconf.Content{
+					{
+						Prefix: "**", // sync everything
+					},
 				},
-			},
-			URLs:         []string{srcBaseURL},
-			PollInterval: updateDuration,
-			TLSVerify:    &tlsVerify,
-			CertDir:      "",
-		}
-
-		defaultVal := true
-		syncConfig := &syncconf.Config{
-			Enable:          &defaultVal,
-			CredentialsFile: credentialsFile,
-			Registries:      []syncconf.RegistryConfig{syncRegistryConfig},
-		}
+				URLs:         []string{srcBaseURL},
+				PollInterval: updateDuration,
+				TLSVerify:    &tlsVerify,
+				CertDir:      "",
+			}
 
-		dctlr, destBaseURL, _, destClient := makeDownstreamServer(t, false, syncConfig)
+			defaultVal := true
+			syncConfig := &syncconf.Config{
+				Enable:          &defaultVal,
+				CredentialsFile: credentialsFile,
+				Registries:      []syncconf.RegistryConfig{syncRegistryConfig},
+			}
 
-		dcm := test.NewControllerManager(dctlr)
-		dcm.StartAndWait(dctlr.Config.HTTP.Port)
+			dctlr, destBaseURL, _, destClient := makeDownstreamServer(t, false, syncConfig)
 
-		defer dcm.StopServer()
+			dcm := test.NewControllerManager(dctlr)
+			dcm.StartAndWait(dctlr.Config.HTTP.Port)
 
-		var (
-			srcTagsList  TagsList
-			destTagsList TagsList
-		)
+			defer dcm.StopServer()
 
-		resp, err := srcClient.R().Get(srcBaseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			var (
+				srcTagsList  TagsList
+				destTagsList TagsList
+			)
 
-		authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			resp, err := srcClient.R().Get(srcBaseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		var goodToken authutils.AccessTokenResponse
+			authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			var goodToken authutils.AccessTokenResponse
 
-		resp, err = srcClient.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Get(srcBaseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		resp, err = srcClient.R().Get(srcBaseURL + "/v2/" + testImage + "/tags/list")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			resp, err = srcClient.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(srcBaseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			resp, err = srcClient.R().Get(srcBaseURL + "/v2/" + testImage + "/tags/list")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		goodToken = authutils.AccessTokenResponse{}
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		resp, err = srcClient.R().SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Get(srcBaseURL + "/v2/" + testImage + "/tags/list")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			goodToken = authutils.AccessTokenResponse{}
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		err = json.Unmarshal(resp.Body(), &srcTagsList)
-		if err != nil {
-			panic(err)
-		}
+			resp, err = srcClient.R().SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(srcBaseURL + "/v2/" + testImage + "/tags/list")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		for {
-			resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/tags/list")
+			err = json.Unmarshal(resp.Body(), &srcTagsList)
 			if err != nil {
 				panic(err)
 			}
 
-			err = json.Unmarshal(resp.Body(), &destTagsList)
-			if err != nil {
-				panic(err)
-			}
+			for {
+				resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/tags/list")
+				if err != nil {
+					panic(err)
+				}
 
-			if len(destTagsList.Tags) > 0 {
-				break
-			}
+				err = json.Unmarshal(resp.Body(), &destTagsList)
+				if err != nil {
+					panic(err)
+				}
 
-			time.Sleep(500 * time.Millisecond)
-		}
+				if len(destTagsList.Tags) > 0 {
+					break
+				}
 
-		So(destTagsList, ShouldResemble, srcTagsList)
+				time.Sleep(500 * time.Millisecond)
+			}
 
-		waitSyncFinish(dctlr.Config.Log.Output)
+			So(destTagsList, ShouldResemble, srcTagsList)
 
-		resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag)
-		So(err, ShouldBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			waitSyncFinish(dctlr.Config.Log.Output)
 
-		// unauthorized namespace
-		resp, err = destClient.R().Get(destBaseURL + "/v2/" + testCveImage + "/manifests/" + testImageTag)
-		So(err, ShouldBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
-	})
+			resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag)
+			So(err, ShouldBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-	Convey("Verify ondemand sync bearer auth", t, func() {
-		// a repo for which clients do not have access, sync shouldn't be able to sync it
-		unauthorizedNamespace := testCveImage
+			// unauthorized namespace
+			resp, err = destClient.R().Get(destBaseURL + "/v2/" + testCveImage + "/manifests/" + testImageTag)
+			So(err, ShouldBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
+		})
 
-		authTestServer := authutils.MakeAuthTestServer(ServerKey, unauthorizedNamespace)
-		defer authTestServer.Close()
+		Convey("Verify ondemand sync bearer auth", t, func() {
+			// a repo for which clients do not have access, sync shouldn't be able to sync it
+			unauthorizedNamespace := testCveImage
 
-		sctlr, srcBaseURL, _, _, srcClient := makeUpstreamServer(t, false, false)
+			var authTestServer *httptest.Server
+			if testCase.useLegacyAuthTestServer {
+				authTestServer = authutils.MakeAuthTestServerLegacy(ServerKey, unauthorizedNamespace)
+			} else {
+				authTestServer = authutils.MakeAuthTestServer(ServerKey, "RS256", unauthorizedNamespace)
+			}
+			defer authTestServer.Close()
 
-		aurl, err := url.Parse(authTestServer.URL)
-		So(err, ShouldBeNil)
+			sctlr, srcBaseURL, _, _, srcClient := makeUpstreamServer(t, false, false)
 
-		sctlr.Config.HTTP.Auth = &config.AuthConfig{
-			Bearer: &config.BearerConfig{
-				Cert:    ServerCert,
-				Realm:   authTestServer.URL + "/auth/token",
-				Service: aurl.Host,
-			},
-		}
+			aurl, err := url.Parse(authTestServer.URL)
+			So(err, ShouldBeNil)
 
-		scm := test.NewControllerManager(sctlr)
-		scm.StartAndWait(sctlr.Config.HTTP.Port)
+			sctlr.Config.HTTP.Auth = &config.AuthConfig{
+				Bearer: &config.BearerConfig{
+					Cert:    ServerCert,
+					Realm:   authTestServer.URL + "/auth/token",
+					Service: aurl.Host,
+				},
+			}
 
-		defer scm.StopServer()
+			scm := test.NewControllerManager(sctlr)
+			scm.StartAndWait(sctlr.Config.HTTP.Port)
 
-		registryName := sync.StripRegistryTransport(srcBaseURL)
-		credentialsFile := makeCredentialsFile(fmt.Sprintf(`{"%s":{"username": "%s", "password": "%s"}}`,
-			registryName, username, password))
+			defer scm.StopServer()
 
-		var tlsVerify bool
+			registryName := sync.StripRegistryTransport(srcBaseURL)
+			credentialsFile := makeCredentialsFile(fmt.Sprintf(`{"%s":{"username": "%s", "password": "%s"}}`,
+				registryName, username, password))
 
-		syncRegistryConfig := syncconf.RegistryConfig{
-			Content: []syncconf.Content{
-				{
-					Prefix: "**", // sync everything
+			var tlsVerify bool
+
+			syncRegistryConfig := syncconf.RegistryConfig{
+				Content: []syncconf.Content{
+					{
+						Prefix: "**", // sync everything
+					},
 				},
-			},
-			URLs:      []string{srcBaseURL},
-			TLSVerify: &tlsVerify,
-			OnDemand:  true,
-			CertDir:   "",
-		}
+				URLs:      []string{srcBaseURL},
+				TLSVerify: &tlsVerify,
+				OnDemand:  true,
+				CertDir:   "",
+			}
 
-		defaultVal := true
-		syncConfig := &syncconf.Config{
-			Enable:          &defaultVal,
-			CredentialsFile: credentialsFile,
-			Registries:      []syncconf.RegistryConfig{syncRegistryConfig},
-		}
+			defaultVal := true
+			syncConfig := &syncconf.Config{
+				Enable:          &defaultVal,
+				CredentialsFile: credentialsFile,
+				Registries:      []syncconf.RegistryConfig{syncRegistryConfig},
+			}
 
-		dctlr, destBaseURL, _, destClient := makeDownstreamServer(t, false, syncConfig)
+			dctlr, destBaseURL, _, destClient := makeDownstreamServer(t, false, syncConfig)
 
-		dcm := test.NewControllerManager(dctlr)
-		dcm.StartAndWait(dctlr.Config.HTTP.Port)
+			dcm := test.NewControllerManager(dctlr)
+			dcm.StartAndWait(dctlr.Config.HTTP.Port)
 
-		defer dcm.StopServer()
+			defer dcm.StopServer()
 
-		var (
-			srcTagsList  TagsList
-			destTagsList TagsList
-		)
+			var (
+				srcTagsList  TagsList
+				destTagsList TagsList
+			)
 
-		resp, err := srcClient.R().Get(srcBaseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			resp, err := srcClient.R().Get(srcBaseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		var goodToken authutils.AccessTokenResponse
+			var goodToken authutils.AccessTokenResponse
 
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		resp, err = srcClient.R().
-			SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Get(srcBaseURL + "/v2/")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			resp, err = srcClient.R().
+				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(srcBaseURL + "/v2/")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		resp, err = srcClient.R().Get(srcBaseURL + "/v2/" + testImage + "/tags/list")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
+			resp, err = srcClient.R().Get(srcBaseURL + "/v2/" + testImage + "/tags/list")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
 
-		authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
-		resp, err = resty.R().
-			SetQueryParam("service", authorizationHeader.Service).
-			SetQueryParam("scope", authorizationHeader.Scope).
-			Get(authorizationHeader.Realm)
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
+			resp, err = resty.R().
+				SetQueryParam("service", authorizationHeader.Service).
+				SetQueryParam("scope", authorizationHeader.Scope).
+				Get(authorizationHeader.Realm)
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		goodToken = authutils.AccessTokenResponse{}
-		err = json.Unmarshal(resp.Body(), &goodToken)
-		So(err, ShouldBeNil)
+			goodToken = authutils.AccessTokenResponse{}
+			err = json.Unmarshal(resp.Body(), &goodToken)
+			So(err, ShouldBeNil)
 
-		resp, err = srcClient.R().SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
-			Get(srcBaseURL + "/v2/" + testImage + "/tags/list")
-		So(err, ShouldBeNil)
-		So(resp, ShouldNotBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			resp, err = srcClient.R().SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
+				Get(srcBaseURL + "/v2/" + testImage + "/tags/list")
+			So(err, ShouldBeNil)
+			So(resp, ShouldNotBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		err = json.Unmarshal(resp.Body(), &srcTagsList)
-		if err != nil {
-			panic(err)
-		}
+			err = json.Unmarshal(resp.Body(), &srcTagsList)
+			if err != nil {
+				panic(err)
+			}
 
-		// sync on demand
-		resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag)
-		So(err, ShouldBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
+			// sync on demand
+			resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag)
+			So(err, ShouldBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
 
-		resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/tags/list")
-		if err != nil {
-			panic(err)
-		}
+			resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/tags/list")
+			if err != nil {
+				panic(err)
+			}
 
-		err = json.Unmarshal(resp.Body(), &destTagsList)
-		if err != nil {
-			panic(err)
-		}
+			err = json.Unmarshal(resp.Body(), &destTagsList)
+			if err != nil {
+				panic(err)
+			}
 
-		So(destTagsList, ShouldResemble, srcTagsList)
+			So(destTagsList, ShouldResemble, srcTagsList)
 
-		// unauthorized namespace
-		resp, err = destClient.R().Get(destBaseURL + "/v2/" + testCveImage + "/manifests/" + testImageTag)
-		So(err, ShouldBeNil)
-		So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
-	})
+			// unauthorized namespace
+			resp, err = destClient.R().Get(destBaseURL + "/v2/" + testCveImage + "/manifests/" + testImageTag)
+			So(err, ShouldBeNil)
+			So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
+		})
+	}
 }
 
 func TestBasicAuth(t *testing.T) {
diff --git a/pkg/test/auth/bearer.go b/pkg/test/auth/bearer.go
index f305d5b0..eada8d5c 100644
--- a/pkg/test/auth/bearer.go
+++ b/pkg/test/auth/bearer.go
@@ -1,15 +1,20 @@
 package auth
 
 import (
+	"crypto"
 	"fmt"
 	"net/http"
 	"net/http/httptest"
+	"os"
 	"regexp"
 	"strings"
 	"time"
 
 	"github.com/chartmuseum/auth"
+	"github.com/golang-jwt/jwt/v5"
 	"github.com/mitchellh/mapstructure"
+
+	"zotregistry.dev/zot/pkg/api"
 )
 
 type (
@@ -24,7 +29,60 @@ type (
 	}
 )
 
-func MakeAuthTestServer(serverKey string, unauthorizedNamespace string) *httptest.Server {
+func MakeAuthTestServer(serverKey, signAlg string, unauthorizedNamespace string) *httptest.Server {
+	signingKey := loadPrivateKeyFromFile(serverKey)
+	signingMethod := jwt.GetSigningMethod(signAlg)
+
+	authTestServer := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
+		var access []api.ResourceAccess
+
+		scope := request.URL.Query().Get("scope")
+		if scope != "" {
+			parts := strings.Split(scope, ":")
+			name := parts[1]
+			actions := strings.Split(parts[2], ",")
+
+			if name == unauthorizedNamespace {
+				actions = []string{}
+			}
+
+			access = []api.ResourceAccess{
+				{
+					Name:    name,
+					Type:    "repository",
+					Actions: actions,
+				},
+			}
+		}
+
+		now := time.Now()
+		claims := api.ClaimsWithAccess{
+			Access: access,
+			RegisteredClaims: jwt.RegisteredClaims{
+				ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 1)),
+				IssuedAt:  jwt.NewNumericDate(now),
+				Issuer:    "Zot",
+				Audience:  []string{"Zot Registry"},
+			},
+		}
+
+		token := jwt.NewWithClaims(signingMethod, claims)
+
+		signedString, err := token.SignedString(signingKey)
+		if err != nil {
+			panic(err)
+		}
+
+		response.Header().Set("Content-Type", "application/json")
+		fmt.Fprintf(response, `{"access_token": "%s"}`, signedString)
+	}))
+
+	return authTestServer
+}
+
+// MakeAuthTestServerLegacy makes a test HTTP server to generate bearer tokens using the github.com/chartmuseum/auth
+// package, to verify backward compatibility of the token authentication process with older versions of zot.
+func MakeAuthTestServerLegacy(serverKey string, unauthorizedNamespace string) *httptest.Server {
 	cmTokenGenerator, err := auth.NewTokenGenerator(&auth.TokenGeneratorOptions{
 		PrivateKeyPath: serverKey,
 		Audience:       "Zot Registry",
@@ -85,3 +143,27 @@ func ParseBearerAuthHeader(authHeaderRaw string) *AuthHeader {
 
 	return &h
 }
+
+func loadPrivateKeyFromFile(path string) crypto.PrivateKey {
+	privateKeyBytes, err := os.ReadFile(path)
+	if err != nil {
+		panic(err)
+	}
+
+	rsaKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
+	if err == nil {
+		return rsaKey
+	}
+
+	ecKey, err := jwt.ParseECPrivateKeyFromPEM(privateKeyBytes)
+	if err == nil {
+		return ecKey
+	}
+
+	edKey, err := jwt.ParseEdPrivateKeyFromPEM(privateKeyBytes)
+	if err == nil {
+		return edKey
+	}
+
+	panic("no valid private key found in file " + path)
+}
diff --git a/pkg/test/auth/bearer_test.go b/pkg/test/auth/bearer_test.go
index 148e2f73..9fcea60c 100644
--- a/pkg/test/auth/bearer_test.go
+++ b/pkg/test/auth/bearer_test.go
@@ -10,6 +10,12 @@ import (
 
 func TestBearerServer(t *testing.T) {
 	Convey("test MakeAuthTestServer() no serve key", t, func() {
-		So(func() { auth.MakeAuthTestServer("", "") }, ShouldPanic)
+		So(func() { auth.MakeAuthTestServer("", "", "") }, ShouldPanic)
+	})
+}
+
+func TestBearerServerLegacy(t *testing.T) {
+	Convey("test MakeAuthTestServerLegacy() no serve key", t, func() {
+		So(func() { auth.MakeAuthTestServerLegacy("", "") }, ShouldPanic)
 	})
 }
diff --git a/test/scripts/gen_certs.sh b/test/scripts/gen_certs.sh
index c52f6247..2351dd90 100755
--- a/test/scripts/gen_certs.sh
+++ b/test/scripts/gen_certs.sh
@@ -2,6 +2,7 @@
 
 set -xe
 
+# RSA
 openssl req \
     -newkey rsa:2048 \
     -nodes \
@@ -45,3 +46,78 @@ openssl x509 \
     -CAkey ca.key \
     -CAcreateserial \
     -out client.cert
+
+# ECDSA
+openssl ecparam \
+    -name prime256v1 \
+    -genkey \
+    -noout \
+    -out ca-ecdsa.key
+
+openssl req \
+    -new \
+    -key ca-ecdsa.key \
+    -nodes \
+    -days 3650 \
+    -x509 \
+    -out ca-ecdsa.crt \
+    -subj "/CN=*"
+
+openssl ecparam \
+    -name prime256v1 \
+    -genkey \
+    -noout \
+    -out server-ecdsa.key
+
+openssl req \
+    -new \
+    -key server-ecdsa.key \
+    -nodes \
+    -out server-ecdsa.csr \
+    -subj "/OU=TestServer/CN=*"
+
+openssl x509 \
+    -req \
+    -days 3650 \
+    -sha256 \
+    -in server-ecdsa.csr \
+    -CA ca-ecdsa.crt \
+    -CAkey ca-ecdsa.key \
+    -CAcreateserial \
+    -out server-ecdsa.cert \
+    -extfile <(echo subjectAltName = IP:127.0.0.1)
+
+# ED25519
+openssl genpkey \
+    -algorithm ed25519 \
+    -out ca-ed25519.key
+
+openssl req \
+    -new \
+    -key ca-ed25519.key \
+    -nodes \
+    -days 3650 \
+    -x509 \
+    -out ca-ed25519.crt \
+    -subj "/CN=*"
+
+openssl genpkey \
+    -algorithm ed25519 \
+    -out server-ed25519.key
+
+openssl req \
+    -new \
+    -key server-ed25519.key \
+    -nodes \
+    -out server-ed25519.csr \
+    -subj "/OU=TestServer/CN=*"
+
+openssl x509 \
+    -req \
+    -days 3650 \
+    -in server-ed25519.csr \
+    -CA ca-ed25519.crt \
+    -CAkey ca-ed25519.key \
+    -CAcreateserial \
+    -out server-ed25519.cert \
+    -extfile <(echo subjectAltName = IP:127.0.0.1)