diff --git a/Makefile b/Makefile
index d238cf9ab..b257bce68 100644
--- a/Makefile
+++ b/Makefile
@@ -98,7 +98,7 @@ VERSIONFILENAME=UIVERSION
 PREPARE_VERSION_NAME=versions
 
 #versions
-REGISTRYVERSION=v2.7.1
+REGISTRYVERSION=v2.7.1-patch-2819
 NGINXVERSION=$(VERSIONTAG)
 NOTARYVERSION=v0.6.1
 CLAIRVERSION=v2.0.7
diff --git a/docs/img/robotaccount/add_robot_account.png b/docs/img/robotaccount/add_robot_account.png
new file mode 100644
index 000000000..316f94bc6
Binary files /dev/null and b/docs/img/robotaccount/add_robot_account.png differ
diff --git a/docs/img/robotaccount/add_robot_account_2.png b/docs/img/robotaccount/add_robot_account_2.png
new file mode 100644
index 000000000..40945a989
Binary files /dev/null and b/docs/img/robotaccount/add_robot_account_2.png differ
diff --git a/docs/img/robotaccount/copy_robot_account_token.png b/docs/img/robotaccount/copy_robot_account_token.png
new file mode 100644
index 000000000..b7c4bdf04
Binary files /dev/null and b/docs/img/robotaccount/copy_robot_account_token.png differ
diff --git a/docs/img/robotaccount/disable_delete_robot_account.png b/docs/img/robotaccount/disable_delete_robot_account.png
new file mode 100644
index 000000000..d387a6559
Binary files /dev/null and b/docs/img/robotaccount/disable_delete_robot_account.png differ
diff --git a/docs/img/robotaccount/set_robot_account_token_duration.png b/docs/img/robotaccount/set_robot_account_token_duration.png
new file mode 100644
index 000000000..e82c15fd1
Binary files /dev/null and b/docs/img/robotaccount/set_robot_account_token_duration.png differ
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index 15d71b5ad..bf0c950bf 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -4899,7 +4899,7 @@ definitions:
   RobotAccountUpdate:
     type: object
     properties:
-      disable:
+      disabled:
         type: boolean
         description: The robot account is disable or enable
   Permission:
diff --git a/docs/user_guide.md b/docs/user_guide.md
index 176959e64..15e4035ea 100644
--- a/docs/user_guide.md
+++ b/docs/user_guide.md
@@ -29,6 +29,7 @@ This guide walks you through the fundamentals of using Harbor. You'll learn how
   * [Working with Helm CLI](#working-with-helm-cli)
 * [Online Garbage Collection.](#online-garbage-collection)
 * [View build history.](#build-history)
+* [Manage robot account of a project.](#robot-account)
 
 ## Role Based Access Control(RBAC)  
 
@@ -597,3 +598,42 @@ Build history make it easy to see the contents of a container image, find the co
 In Harbor portal, enter your project, select the repository, click on the link of tag name you'd like to see its build history, the detail page will be opened. Then switch to `Build History` tab, you can see the build history information.
 
 ![build_ history](img/build_history.png)
+
+## Robot Account
+Robot Accounts are accounts created by project admins that are intended for automated operations. They have the following limitations:
+
+1, Robot Accounts cannot login Harbor portal
+2, Robot Accounts can only perform `docker push`/`docker pull` operations with a token.
+
+### Add a Robot Account
+If you are a project admin, you can create a Robot Account by clicking "New Robot Account" in the `Robot Accounts` tab of a project, and enter a name, a description and permission.
+![add_robot_account](img/robotaccount/add_robot_account.png)
+
+![add_robot_account](img/robotaccount/add_robot_account_2.png)
+
+> **NOTE:** The name will become `robot$<accountname>` and will be used to distinguish a robot account from a normal harbor user.
+
+![copy_robot_account_token](img/robotaccount/copy_robot_account_token.png)
+As Harbor doesn't store your account token, please make sure to copy it in the pop up dialog after creating, otherwise, there is no way to get it from Harbor.
+
+### Configure duration of robot account
+If you are a system admin, you can configure the robot account token duration in days. 
+![set_robot_account_token_duration](img/robotaccount/set_robot_account_token_duration.png)
+
+### Authenticate with a robot account
+To authenticate with a Robot Account, use `docker login` as below,
+
+```
+docker login harbor.io
+Username: robot$accountname
+Password: Thepasswordgeneratedbyprojectadmin
+```
+
+### Disable a robot account
+If you are a project admin, you can disable a Robot Account by clicking "Disable Account" in the `Robot Accounts` tab of a project.
+![disable_robot_account](img/robotaccount/disable_delete_robot_account.png)
+
+### Delete a robot account
+If you are a project admin, you can delete a Robot Account by clicking "Delete" in the `Robot Accounts` tab of a project.
+![delete_robot_account](img/robotaccount/disable_delete_robot_account.png)
+
diff --git a/make/install.sh b/make/install.sh
index b01da719c..e7e94265f 100755
--- a/make/install.sh
+++ b/make/install.sh
@@ -192,7 +192,7 @@ docker-compose up -d
 protocol=http
 hostname=reg.mydomain.com
 
-if [ -n "$(grep '^[^#]*https:' ./harbor.yml)"]
+if [ -n "$(grep '^[^#]*https:' ./harbor.yml)" ]
 then
 protocol=https
 fi
diff --git a/make/migrations/postgresql/0004_1.8.0_schema.up.sql b/make/migrations/postgresql/0004_1.8.0_schema.up.sql
index 94c7e6a26..83bc90e67 100644
--- a/make/migrations/postgresql/0004_1.8.0_schema.up.sql
+++ b/make/migrations/postgresql/0004_1.8.0_schema.up.sql
@@ -16,6 +16,9 @@ CREATE TRIGGER robot_update_time_at_modtime BEFORE UPDATE ON robot FOR EACH ROW
 CREATE TABLE oidc_user (
  id SERIAL NOT NULL,
  user_id int NOT NULL,
+ /*
+ Encoded secret
+  */
  secret varchar(255) NOT NULL,
   /*
  Subject and Issuer
@@ -24,9 +27,14 @@ CREATE TABLE oidc_user (
   The sub (subject) and iss (issuer) Claims, used together, are the only Claims that an RP can rely upon as a stable identifier for the End-User
  */
  subiss varchar(255) NOT NULL,
+ /*
+ Encoded token
+  */
+ token text,
  creation_time timestamp default CURRENT_TIMESTAMP,
  update_time timestamp default CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
+ FOREIGN KEY (user_id) REFERENCES harbor_user(user_id),
  UNIQUE (subiss)
 );
 
diff --git a/make/photon/prepare/templates/docker_compose/docker-compose.yml.jinja b/make/photon/prepare/templates/docker_compose/docker-compose.yml.jinja
index b290a543c..d0359c9f0 100644
--- a/make/photon/prepare/templates/docker_compose/docker-compose.yml.jinja
+++ b/make/photon/prepare/templates/docker_compose/docker-compose.yml.jinja
@@ -261,8 +261,12 @@ services:
     dns_search: .
     ports:
       - {{http_port}}:80
+{% if protocol == 'https' %}
       - {{https_port}}:443
+{% endif %}
+{% if with_notary %}
       - 4443:4443
+{% endif %}
     depends_on:
       - postgresql
       - registry
diff --git a/make/photon/prepare/templates/registry/config.yml.jinja b/make/photon/prepare/templates/registry/config.yml.jinja
index 8af33b2d3..2d15eba77 100644
--- a/make/photon/prepare/templates/registry/config.yml.jinja
+++ b/make/photon/prepare/templates/registry/config.yml.jinja
@@ -37,3 +37,6 @@ notifications:
     timeout: 3000ms
     threshold: 5
     backoff: 1s
+compatibility:
+  schema1:
+    enabled: true
\ No newline at end of file
diff --git a/make/photon/registry/Dockerfile.binary b/make/photon/registry/Dockerfile.binary
index 41fad81c7..de9aa4bdf 100644
--- a/make/photon/registry/Dockerfile.binary
+++ b/make/photon/registry/Dockerfile.binary
@@ -1,7 +1,7 @@
 FROM golang:1.11
 
 ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
-ENV DOCKER_BUILDTAGS include_oss include_gcs
+ENV BUILDTAGS include_oss include_gcs
 
 WORKDIR $DISTRIBUTION_DIR
 COPY . $DISTRIBUTION_DIR
diff --git a/src/common/api/base.go b/src/common/api/base.go
index c849bb70a..9aa16a618 100644
--- a/src/common/api/base.go
+++ b/src/common/api/base.go
@@ -24,6 +24,7 @@ import (
 	commonhttp "github.com/goharbor/harbor/src/common/http"
 	"github.com/goharbor/harbor/src/common/utils/log"
 
+	"errors"
 	"github.com/astaxie/beego"
 )
 
@@ -48,68 +49,6 @@ func (b *BaseAPI) GetInt64FromPath(key string) (int64, error) {
 	return strconv.ParseInt(value, 10, 64)
 }
 
-// HandleNotFound ...
-func (b *BaseAPI) HandleNotFound(text string) {
-	log.Info(text)
-	b.RenderError(http.StatusNotFound, text)
-}
-
-// HandleUnauthorized ...
-func (b *BaseAPI) HandleUnauthorized() {
-	log.Info("unauthorized")
-	b.RenderError(http.StatusUnauthorized, "")
-}
-
-// HandleForbidden ...
-func (b *BaseAPI) HandleForbidden(text string) {
-	log.Infof("forbidden: %s", text)
-	b.RenderError(http.StatusForbidden, text)
-}
-
-// HandleBadRequest ...
-func (b *BaseAPI) HandleBadRequest(text string) {
-	log.Info(text)
-	b.RenderError(http.StatusBadRequest, text)
-}
-
-// HandleStatusPreconditionFailed ...
-func (b *BaseAPI) HandleStatusPreconditionFailed(text string) {
-	log.Info(text)
-	b.RenderError(http.StatusPreconditionFailed, text)
-}
-
-// HandleConflict ...
-func (b *BaseAPI) HandleConflict(text ...string) {
-	msg := ""
-	if len(text) > 0 {
-		msg = text[0]
-	}
-	log.Infof("conflict: %s", msg)
-
-	b.RenderError(http.StatusConflict, msg)
-}
-
-// HandleInternalServerError ...
-func (b *BaseAPI) HandleInternalServerError(text string) {
-	log.Error(text)
-	b.RenderError(http.StatusInternalServerError, "")
-}
-
-// ParseAndHandleError : if the err is an instance of utils/error.Error,
-// return the status code and the detail message contained in err, otherwise
-// return 500
-func (b *BaseAPI) ParseAndHandleError(text string, err error) {
-	if err == nil {
-		return
-	}
-	log.Errorf("%s: %v", text, err)
-	if e, ok := err.(*commonhttp.Error); ok {
-		b.RenderError(e.Code, e.Message)
-		return
-	}
-	b.RenderError(http.StatusInternalServerError, "")
-}
-
 // Render returns nil as it won't render template
 func (b *BaseAPI) Render() error {
 	return nil
@@ -120,23 +59,35 @@ func (b *BaseAPI) RenderError(code int, text string) {
 	http.Error(b.Ctx.ResponseWriter, text, code)
 }
 
+// RenderFormattedError renders errors with well formatted style
+func (b *BaseAPI) RenderFormattedError(errorCode int, errorMsg string) {
+	error := commonhttp.Error{
+		Code:    errorCode,
+		Message: errorMsg,
+	}
+	formattedErrMsg := error.String()
+	log.Errorf("%s %s failed with error: %s", b.Ctx.Request.Method, b.Ctx.Request.URL.String(), formattedErrMsg)
+	b.RenderError(error.Code, formattedErrMsg)
+}
+
 // DecodeJSONReq decodes a json request
-func (b *BaseAPI) DecodeJSONReq(v interface{}) {
+func (b *BaseAPI) DecodeJSONReq(v interface{}) error {
 	err := json.Unmarshal(b.Ctx.Input.CopyBody(1<<32), v)
 	if err != nil {
 		log.Errorf("Error while decoding the json request, error: %v, %v",
 			err, string(b.Ctx.Input.CopyBody(1 << 32)[:]))
-		b.CustomAbort(http.StatusBadRequest, "Invalid json request")
+		return errors.New("Invalid json request")
 	}
+	return nil
 }
 
 // Validate validates v if it implements interface validation.ValidFormer
-func (b *BaseAPI) Validate(v interface{}) {
+func (b *BaseAPI) Validate(v interface{}) (bool, error) {
 	validator := validation.Validation{}
 	isValid, err := validator.Valid(v)
 	if err != nil {
 		log.Errorf("failed to validate: %v", err)
-		b.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		return false, err
 	}
 
 	if !isValid {
@@ -144,14 +95,17 @@ func (b *BaseAPI) Validate(v interface{}) {
 		for _, e := range validator.Errors {
 			message += fmt.Sprintf("%s %s \n", e.Field, e.Message)
 		}
-		b.CustomAbort(http.StatusBadRequest, message)
+		return false, errors.New(message)
 	}
+	return true, nil
 }
 
 // DecodeJSONReqAndValidate does both decoding and validation
-func (b *BaseAPI) DecodeJSONReqAndValidate(v interface{}) {
-	b.DecodeJSONReq(v)
-	b.Validate(v)
+func (b *BaseAPI) DecodeJSONReqAndValidate(v interface{}) (bool, error) {
+	if err := b.DecodeJSONReq(v); err != nil {
+		return false, err
+	}
+	return b.Validate(v)
 }
 
 // Redirect does redirection to resource URI with http header status code.
@@ -163,18 +117,18 @@ func (b *BaseAPI) Redirect(statusCode int, resouceID string) {
 }
 
 // GetIDFromURL checks the ID in request URL
-func (b *BaseAPI) GetIDFromURL() int64 {
+func (b *BaseAPI) GetIDFromURL() (int64, error) {
 	idStr := b.Ctx.Input.Param(":id")
 	if len(idStr) == 0 {
-		b.CustomAbort(http.StatusBadRequest, "invalid ID in URL")
+		return 0, errors.New("invalid ID in URL")
 	}
 
 	id, err := strconv.ParseInt(idStr, 10, 64)
 	if err != nil || id <= 0 {
-		b.CustomAbort(http.StatusBadRequest, "invalid ID in URL")
+		return 0, errors.New("invalid ID in URL")
 	}
 
-	return id
+	return id, nil
 }
 
 // SetPaginationHeader set"Link" and "X-Total-Count" header for pagination request
@@ -213,15 +167,15 @@ func (b *BaseAPI) SetPaginationHeader(total, page, pageSize int64) {
 }
 
 // GetPaginationParams ...
-func (b *BaseAPI) GetPaginationParams() (page, pageSize int64) {
-	page, err := b.GetInt64("page", 1)
+func (b *BaseAPI) GetPaginationParams() (page, pageSize int64, err error) {
+	page, err = b.GetInt64("page", 1)
 	if err != nil || page <= 0 {
-		b.CustomAbort(http.StatusBadRequest, "invalid page")
+		return 0, 0, errors.New("invalid page")
 	}
 
 	pageSize, err = b.GetInt64("page_size", defaultPageSize)
 	if err != nil || pageSize <= 0 {
-		b.CustomAbort(http.StatusBadRequest, "invalid page_size")
+		return 0, 0, errors.New("invalid page_size")
 	}
 
 	if pageSize > maxPageSize {
@@ -229,5 +183,60 @@ func (b *BaseAPI) GetPaginationParams() (page, pageSize int64) {
 		log.Debugf("the parameter page_size %d exceeds the max %d, set it to max", pageSize, maxPageSize)
 	}
 
-	return page, pageSize
+	return page, pageSize, nil
+}
+
+// ParseAndHandleError : if the err is an instance of utils/error.Error,
+// return the status code and the detail message contained in err, otherwise
+// return 500
+func (b *BaseAPI) ParseAndHandleError(text string, err error) {
+	if err == nil {
+		return
+	}
+	log.Errorf("%s: %v", text, err)
+	if e, ok := err.(*commonhttp.Error); ok {
+		b.RenderFormattedError(e.Code, e.Message)
+		return
+	}
+	b.SendInternalServerError(errors.New(""))
+}
+
+// SendUnAuthorizedError sends unauthorized error to the client.
+func (b *BaseAPI) SendUnAuthorizedError(err error) {
+	b.RenderFormattedError(http.StatusUnauthorized, err.Error())
+}
+
+// SendConflictError sends conflict error to the client.
+func (b *BaseAPI) SendConflictError(err error) {
+	b.RenderFormattedError(http.StatusConflict, err.Error())
+}
+
+// SendNotFoundError sends not found error to the client.
+func (b *BaseAPI) SendNotFoundError(err error) {
+	b.RenderFormattedError(http.StatusNotFound, err.Error())
+}
+
+// SendBadRequestError sends bad request error to the client.
+func (b *BaseAPI) SendBadRequestError(err error) {
+	b.RenderFormattedError(http.StatusBadRequest, err.Error())
+}
+
+// SendInternalServerError sends internal server error to the client.
+func (b *BaseAPI) SendInternalServerError(err error) {
+	b.RenderFormattedError(http.StatusInternalServerError, err.Error())
+}
+
+// SendForbiddenError sends forbidden error to the client.
+func (b *BaseAPI) SendForbiddenError(err error) {
+	b.RenderFormattedError(http.StatusForbidden, err.Error())
+}
+
+// SendPreconditionFailedError sends forbidden error to the client.
+func (b *BaseAPI) SendPreconditionFailedError(err error) {
+	b.RenderFormattedError(http.StatusPreconditionFailed, err.Error())
+}
+
+// SendStatusServiceUnavailableError sends forbidden error to the client.
+func (b *BaseAPI) SendStatusServiceUnavailableError(err error) {
+	b.RenderFormattedError(http.StatusServiceUnavailable, err.Error())
 }
diff --git a/src/common/config/keyprovider.go b/src/common/config/keyprovider.go
index eac95612d..163ced369 100644
--- a/src/common/config/keyprovider.go
+++ b/src/common/config/keyprovider.go
@@ -46,3 +46,13 @@ func (f *FileKeyProvider) Get(params map[string]interface{}) (string, error) {
 	}
 	return string(b), nil
 }
+
+// PresetKeyProvider returns the preset key disregarding the parm, this is for testing only
+type PresetKeyProvider struct {
+	Key string
+}
+
+// Get ...
+func (p *PresetKeyProvider) Get(params map[string]interface{}) (string, error) {
+	return p.Key, nil
+}
diff --git a/src/common/config/keyprovider_test.go b/src/common/config/keyprovider_test.go
index 48127d903..c3a025ff2 100644
--- a/src/common/config/keyprovider_test.go
+++ b/src/common/config/keyprovider_test.go
@@ -15,6 +15,7 @@
 package config
 
 import (
+	"github.com/stretchr/testify/assert"
 	"io/ioutil"
 	"os"
 	"testing"
@@ -42,3 +43,12 @@ func TestGetOfFileKeyProvider(t *testing.T) {
 		return
 	}
 }
+
+func TestPresetKeyProvider(t *testing.T) {
+	kp := &PresetKeyProvider{
+		Key: "mykey",
+	}
+	k, err := kp.Get(nil)
+	assert.Nil(t, err)
+	assert.Equal(t, "mykey", k)
+}
diff --git a/src/common/config/metadata/metadatalist.go b/src/common/config/metadata/metadatalist.go
index 89f037d80..9d08351c2 100644
--- a/src/common/config/metadata/metadatalist.go
+++ b/src/common/config/metadata/metadatalist.go
@@ -133,7 +133,7 @@ var (
 
 		{Name: common.HTTPAuthProxyEndpoint, Scope: UserScope, Group: HTTPAuthGroup, ItemType: &StringType{}},
 		{Name: common.HTTPAuthProxyTokenReviewEndpoint, Scope: UserScope, Group: HTTPAuthGroup, ItemType: &StringType{}},
-		{Name: common.HTTPAuthProxySkipCertVerify, Scope: UserScope, Group: HTTPAuthGroup, DefaultValue: "false", ItemType: &BoolType{}},
+		{Name: common.HTTPAuthProxyVerifyCert, Scope: UserScope, Group: HTTPAuthGroup, DefaultValue: "true", ItemType: &BoolType{}},
 		{Name: common.HTTPAuthProxyAlwaysOnboard, Scope: UserScope, Group: HTTPAuthGroup, DefaultValue: "false", ItemType: &BoolType{}},
 
 		{Name: common.OIDCName, Scope: UserScope, Group: OIDCGroup, ItemType: &StringType{}},
@@ -141,7 +141,7 @@ var (
 		{Name: common.OIDCCLientID, Scope: UserScope, Group: OIDCGroup, ItemType: &StringType{}},
 		{Name: common.OIDCClientSecret, Scope: UserScope, Group: OIDCGroup, ItemType: &PasswordType{}},
 		{Name: common.OIDCScope, Scope: UserScope, Group: OIDCGroup, ItemType: &StringType{}},
-		{Name: common.OIDCSkipCertVerify, Scope: UserScope, Group: OIDCGroup, DefaultValue: "false", ItemType: &BoolType{}},
+		{Name: common.OIDCVerifyCert, Scope: UserScope, Group: OIDCGroup, DefaultValue: "true", ItemType: &BoolType{}},
 
 		{Name: "with_chartmuseum", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_CHARTMUSEUM", DefaultValue: "false", ItemType: &BoolType{}, Editable: true},
 		{Name: "with_clair", Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_CLAIR", DefaultValue: "false", ItemType: &BoolType{}, Editable: true},
diff --git a/src/common/const.go b/src/common/const.go
index 18fb743f5..9a6ad2560 100644
--- a/src/common/const.go
+++ b/src/common/const.go
@@ -100,13 +100,13 @@ const (
 	UAAVerifyCert                    = "uaa_verify_cert"
 	HTTPAuthProxyEndpoint            = "http_authproxy_endpoint"
 	HTTPAuthProxyTokenReviewEndpoint = "http_authproxy_tokenreview_endpoint"
-	HTTPAuthProxySkipCertVerify      = "http_authproxy_skip_cert_verify"
+	HTTPAuthProxyVerifyCert          = "http_authproxy_verify_cert"
 	HTTPAuthProxyAlwaysOnboard       = "http_authproxy_always_onboard"
 	OIDCName                         = "oidc_name"
 	OIDCEndpoint                     = "oidc_endpoint"
 	OIDCCLientID                     = "oidc_client_id"
 	OIDCClientSecret                 = "oidc_client_secret"
-	OIDCSkipCertVerify               = "oidc_skip_cert_verify"
+	OIDCVerifyCert                   = "oidc_verify_cert"
 	OIDCScope                        = "oidc_scope"
 
 	DefaultClairEndpoint              = "http://clair:6060"
diff --git a/src/common/http/error.go b/src/common/http/error.go
index 9ca64203d..584d29f68 100644
--- a/src/common/http/error.go
+++ b/src/common/http/error.go
@@ -15,16 +15,26 @@
 package http
 
 import (
+	"encoding/json"
 	"fmt"
 )
 
 // Error wrap HTTP status code and message as an error
 type Error struct {
-	Code    int
-	Message string
+	Code    int    `json:"code"`
+	Message string `json:"message"`
 }
 
 // Error ...
 func (e *Error) Error() string {
 	return fmt.Sprintf("http error: code %d, message %s", e.Code, e.Message)
 }
+
+// String wraps the error msg to the well formatted error message
+func (e *Error) String() string {
+	data, err := json.Marshal(&e)
+	if err != nil {
+		return e.Message
+	}
+	return string(data)
+}
diff --git a/src/common/http/error_test.go b/src/common/http/error_test.go
new file mode 100644
index 000000000..e2b7488da
--- /dev/null
+++ b/src/common/http/error_test.go
@@ -0,0 +1,17 @@
+package http
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+// Test case for error wrapping function.
+func TestWrapError(t *testing.T) {
+	err := Error{
+		Code:    1,
+		Message: "test",
+	}
+
+	assert.Equal(t, err.String(), "{\"code\":1,\"message\":\"test\"}")
+
+}
diff --git a/src/common/models/config.go b/src/common/models/config.go
index 8d757256d..cbcb3f810 100644
--- a/src/common/models/config.go
+++ b/src/common/models/config.go
@@ -69,19 +69,19 @@ type Email struct {
 type HTTPAuthProxy struct {
 	Endpoint            string `json:"endpoint"`
 	TokenReviewEndpoint string `json:"tokenreivew_endpoint"`
-	SkipCertVerify      bool   `json:"skip_cert_verify"`
+	VerifyCert          bool   `json:"verify_cert"`
 	AlwaysOnBoard       bool   `json:"always_onboard"`
 }
 
 // OIDCSetting wraps the settings for OIDC auth endpoint
 type OIDCSetting struct {
-	Name           string   `json:"name"`
-	Endpoint       string   `json:"endpoint"`
-	SkipCertVerify bool     `json:"skip_cert_verify"`
-	ClientID       string   `json:"client_id"`
-	ClientSecret   string   `json:"client_secret"`
-	RedirectURL    string   `json:"redirect_url"`
-	Scope          []string `json:"scope"`
+	Name         string   `json:"name"`
+	Endpoint     string   `json:"endpoint"`
+	VerifyCert   bool     `json:"verify_cert"`
+	ClientID     string   `json:"client_id"`
+	ClientSecret string   `json:"client_secret"`
+	RedirectURL  string   `json:"redirect_url"`
+	Scope        []string `json:"scope"`
 }
 
 // ConfigEntry ...
diff --git a/src/common/models/oidc_user.go b/src/common/models/oidc_user.go
index 5d6c66c35..e42ae3e54 100644
--- a/src/common/models/oidc_user.go
+++ b/src/common/models/oidc_user.go
@@ -6,10 +6,14 @@ import (
 
 // OIDCUser ...
 type OIDCUser struct {
-	ID           int64     `orm:"pk;auto;column(id)" json:"id"`
-	UserID       int       `orm:"column(user_id)" json:"user_id"`
-	Secret       string    `orm:"column(secret)" json:"secret"`
+	ID     int64 `orm:"pk;auto;column(id)" json:"id"`
+	UserID int   `orm:"column(user_id)" json:"user_id"`
+	// encrypted secret
+	Secret string `orm:"column(secret)" json:"-"`
+	// secret in plain text
+	PlainSecret  string    `orm:"-" json:"secret"`
 	SubIss       string    `orm:"column(subiss)" json:"subiss"`
+	Token        string    `orm:"column(token)" json:"-"`
 	CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
 	UpdateTime   time.Time `orm:"column(update_time);auto_now" json:"update_time"`
 }
diff --git a/src/common/token/claims.go b/src/common/token/claims.go
index f8cd2dc65..bec9f5f2f 100644
--- a/src/common/token/claims.go
+++ b/src/common/token/claims.go
@@ -1,9 +1,9 @@
 package token
 
 import (
+	"errors"
 	"github.com/dgrijalva/jwt-go"
 	"github.com/goharbor/harbor/src/common/rbac"
-	"github.com/pkg/errors"
 )
 
 // RobotClaims implements the interface of jwt.Claims
diff --git a/src/common/utils/oidc/helper.go b/src/common/utils/oidc/helper.go
index 75a45f6b1..efa276dd6 100644
--- a/src/common/utils/oidc/helper.go
+++ b/src/common/utils/oidc/helper.go
@@ -41,14 +41,14 @@ type providerHelper struct {
 }
 
 type endpoint struct {
-	url            string
-	skipCertVerify bool
+	url        string
+	VerifyCert bool
 }
 
 func (p *providerHelper) get() (*gooidc.Provider, error) {
 	if p.instance.Load() != nil {
 		s := p.setting.Load().(models.OIDCSetting)
-		if s.Endpoint != p.ep.url || s.SkipCertVerify != p.ep.skipCertVerify { // relevant settings have changed, need to re-create provider.
+		if s.Endpoint != p.ep.url || s.VerifyCert != p.ep.VerifyCert { // relevant settings have changed, need to re-create provider.
 			if err := p.create(); err != nil {
 				return nil, err
 			}
@@ -90,24 +90,15 @@ func (p *providerHelper) create() error {
 		return errors.New("the configuration is not loaded")
 	}
 	s := p.setting.Load().(models.OIDCSetting)
-	var client *http.Client
-	if s.SkipCertVerify {
-		client = &http.Client{
-			Transport: insecureTransport,
-		}
-	} else {
-		client = &http.Client{}
-	}
-	ctx := context.Background()
-	gooidc.ClientContext(ctx, client)
+	ctx := clientCtx(context.Background(), s.VerifyCert)
 	provider, err := gooidc.NewProvider(ctx, s.Endpoint)
 	if err != nil {
 		return fmt.Errorf("failed to create OIDC provider, error: %v", err)
 	}
 	p.instance.Store(provider)
 	p.ep = endpoint{
-		url:            s.Endpoint,
-		skipCertVerify: s.SkipCertVerify,
+		url:        s.Endpoint,
+		VerifyCert: s.VerifyCert,
 	}
 	return nil
 }
@@ -170,6 +161,8 @@ func ExchangeToken(ctx context.Context, code string) (*Token, error) {
 		log.Errorf("Failed to get OAuth configuration, error: %v", err)
 		return nil, err
 	}
+	setting := provider.setting.Load().(models.OIDCSetting)
+	ctx = clientCtx(ctx, setting.VerifyCert)
 	oauthToken, err := oauth.Exchange(ctx, code)
 	if err != nil {
 		return nil, err
@@ -184,5 +177,36 @@ func VerifyToken(ctx context.Context, rawIDToken string) (*gooidc.IDToken, error
 		return nil, err
 	}
 	verifier := p.Verifier(&gooidc.Config{ClientID: provider.setting.Load().(models.OIDCSetting).ClientID})
+	setting := provider.setting.Load().(models.OIDCSetting)
+	ctx = clientCtx(ctx, setting.VerifyCert)
 	return verifier.Verify(ctx, rawIDToken)
 }
+
+func clientCtx(ctx context.Context, verifyCert bool) context.Context {
+	var client *http.Client
+	if !verifyCert {
+		client = &http.Client{
+			Transport: insecureTransport,
+		}
+	} else {
+		client = &http.Client{}
+	}
+	return gooidc.ClientContext(ctx, client)
+}
+
+// RefreshToken refreshes the token passed in parameter, and return the new token.
+func RefreshToken(ctx context.Context, token *Token) (*Token, error) {
+	oauth, err := getOauthConf()
+	if err != nil {
+		log.Errorf("Failed to get OAuth configuration, error: %v", err)
+		return nil, err
+	}
+	setting := provider.setting.Load().(models.OIDCSetting)
+	ctx = clientCtx(ctx, setting.VerifyCert)
+	ts := oauth.TokenSource(ctx, token.Token)
+	t, err := ts.Token()
+	if err != nil {
+		return nil, err
+	}
+	return &Token{Token: t, IDToken: t.Extra("id_token").(string)}, nil
+}
diff --git a/src/common/utils/oidc/helper_test.go b/src/common/utils/oidc/helper_test.go
index 6e4f357e2..e1e71a8b9 100644
--- a/src/common/utils/oidc/helper_test.go
+++ b/src/common/utils/oidc/helper_test.go
@@ -16,6 +16,7 @@ package oidc
 
 import (
 	"github.com/goharbor/harbor/src/common"
+	config2 "github.com/goharbor/harbor/src/common/config"
 	"github.com/goharbor/harbor/src/common/models"
 	"github.com/goharbor/harbor/src/core/config"
 	"github.com/stretchr/testify/assert"
@@ -28,16 +29,17 @@ import (
 
 func TestMain(m *testing.M) {
 	conf := map[string]interface{}{
-		common.OIDCName:           "test",
-		common.OIDCEndpoint:       "https://accounts.google.com",
-		common.OIDCSkipCertVerify: "false",
-		common.OIDCScope:          "openid, profile, offline_access",
-		common.OIDCCLientID:       "client",
-		common.OIDCClientSecret:   "secret",
-		common.ExtEndpoint:        "https://harbor.test",
+		common.OIDCName:         "test",
+		common.OIDCEndpoint:     "https://accounts.google.com",
+		common.OIDCVerifyCert:   "true",
+		common.OIDCScope:        "openid, profile, offline_access",
+		common.OIDCCLientID:     "client",
+		common.OIDCClientSecret: "secret",
+		common.ExtEndpoint:      "https://harbor.test",
 	}
+	kp := &config2.PresetKeyProvider{Key: "naa4JtarA1Zsc3uY"}
 
-	config.InitWithSettings(conf)
+	config.InitWithSettings(conf, kp)
 
 	result := m.Run()
 	if result != 0 {
@@ -71,13 +73,13 @@ func TestHelperGet(t *testing.T) {
 	assert.Equal(t, "https://oauth2.googleapis.com/token", p.Endpoint().TokenURL)
 
 	update := map[string]interface{}{
-		common.OIDCName:           "test",
-		common.OIDCEndpoint:       "https://accounts.google.com",
-		common.OIDCSkipCertVerify: "false",
-		common.OIDCScope:          "openid, profile, offline_access",
-		common.OIDCCLientID:       "client",
-		common.OIDCClientSecret:   "new-secret",
-		common.ExtEndpoint:        "https://harbor.test",
+		common.OIDCName:         "test",
+		common.OIDCEndpoint:     "https://accounts.google.com",
+		common.OIDCVerifyCert:   "true",
+		common.OIDCScope:        "openid, profile, offline_access",
+		common.OIDCCLientID:     "client",
+		common.OIDCClientSecret: "new-secret",
+		common.ExtEndpoint:      "https://harbor.test",
 	}
 	config.GetCfgManager().UpdateConfig(update)
 
diff --git a/src/common/utils/oidc/secret.go b/src/common/utils/oidc/secret.go
new file mode 100644
index 000000000..d98037d73
--- /dev/null
+++ b/src/common/utils/oidc/secret.go
@@ -0,0 +1,130 @@
+package oidc
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"github.com/goharbor/harbor/src/common/dao"
+	"github.com/goharbor/harbor/src/common/utils"
+	"github.com/goharbor/harbor/src/common/utils/log"
+	"github.com/goharbor/harbor/src/core/config"
+	"github.com/pkg/errors"
+	"sync"
+)
+
+// SecretVerifyError wraps the different errors happened when verifying a secret for OIDC user.  When seeing this error,
+// the caller should consider this an authentication error.
+type SecretVerifyError struct {
+	cause error
+}
+
+func (se *SecretVerifyError) Error() string {
+	return fmt.Sprintf("failed to verify the secret: %v", se.cause)
+}
+
+func verifyError(err error) error {
+	return &SecretVerifyError{err}
+}
+
+// SecretManager is the interface for store and verify the secret
+type SecretManager interface {
+	// SetSecret sets the secret and token based on the ID of the user, when setting the secret the user has to be
+	// onboarded to Harbor DB.
+	SetSecret(userID int, secret string, token *Token) error
+	// VerifySecret verifies the secret and the token associated with it, it refreshes the token in the DB if it's
+	// refreshed during the verification
+	VerifySecret(ctx context.Context, userID int, secret string) error
+}
+
+type defaultManager struct {
+	sync.Mutex
+	key string
+}
+
+var m SecretManager = &defaultManager{}
+
+func (dm *defaultManager) getEncryptKey() (string, error) {
+	if dm.key == "" {
+		dm.Lock()
+		defer dm.Unlock()
+		if dm.key == "" {
+			key, err := config.SecretKey()
+			if err != nil {
+				return "", err
+			}
+			dm.key = key
+		}
+	}
+	return dm.key, nil
+}
+
+// SetSecret sets the secret and token based on the ID of the user, when setting the secret the user has to be
+// onboarded to Harbor DB.
+func (dm *defaultManager) SetSecret(userID int, secret string, token *Token) error {
+	key, err := dm.getEncryptKey()
+	if err != nil {
+		return fmt.Errorf("failed to load the key for encryption/decryption: %v", err)
+	}
+	oidcUser, err := dao.GetOIDCUserByUserID(userID)
+	if oidcUser == nil {
+		return fmt.Errorf("failed to get oidc user info, error: %v", err)
+	}
+	encSecret, _ := utils.ReversibleEncrypt(secret, key)
+	tb, _ := json.Marshal(token)
+	encToken, _ := utils.ReversibleEncrypt(string(tb), key)
+	oidcUser.Secret = encSecret
+	oidcUser.Token = encToken
+	return dao.UpdateOIDCUser(oidcUser)
+}
+
+// VerifySecret verifies the secret and the token associated with it, it tries to update the token in the DB if it's
+// refreshed during the verification
+func (dm *defaultManager) VerifySecret(ctx context.Context, userID int, secret string) error {
+	oidcUser, err := dao.GetOIDCUserByUserID(userID)
+	if err != nil {
+		return fmt.Errorf("failed to get oidc user info, error: %v", err)
+	}
+	if oidcUser == nil {
+		return fmt.Errorf("user is not onboarded as OIDC user")
+	}
+	key, err := dm.getEncryptKey()
+	if err != nil {
+		return fmt.Errorf("failed to load the key for encryption/decryption: %v", err)
+	}
+	plainSecret, err := utils.ReversibleDecrypt(oidcUser.Secret, key)
+	if err != nil {
+		return fmt.Errorf("failed to decrypt secret from DB: %v", err)
+	}
+	if secret != plainSecret {
+		return verifyError(errors.New("secret mismatch"))
+	}
+	tokenStr, err := utils.ReversibleDecrypt(oidcUser.Token, key)
+	if err != nil {
+		return verifyError(err)
+	}
+	token := &Token{}
+	err = json.Unmarshal(([]byte)(tokenStr), token)
+	if err != nil {
+		return verifyError(err)
+	}
+	_, err = VerifyToken(ctx, token.IDToken)
+	if err == nil {
+		return nil
+	}
+	log.Infof("Failed to verify ID Token, error: %v, refreshing...", err)
+	t, err := RefreshToken(ctx, token)
+	if err != nil {
+		return verifyError(err)
+	}
+	err = dm.SetSecret(oidcUser.UserID, secret, t)
+	if err != nil {
+		log.Warningf("Failed to update the token in DB: %v, ignore this error.", err)
+	}
+	return nil
+}
+
+// VerifySecret verifies the secret and the token associated with it, it tries to update the token in the DB if it's
+// refreshed during the verification
+func VerifySecret(ctx context.Context, userID int, secret string) error {
+	return m.VerifySecret(ctx, userID, secret)
+}
diff --git a/src/common/utils/oidc/secret_test.go b/src/common/utils/oidc/secret_test.go
new file mode 100644
index 000000000..1a376f54f
--- /dev/null
+++ b/src/common/utils/oidc/secret_test.go
@@ -0,0 +1,32 @@
+package oidc
+
+import (
+	"context"
+	"fmt"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestSecretVerifyError(t *testing.T) {
+	sve := &SecretVerifyError{cause: fmt.Errorf("myerror")}
+	assert.Equal(t, "failed to verify the secret: myerror", sve.Error())
+	err := verifyError(fmt.Errorf("myerror"))
+	assert.Equal(t, sve, err)
+}
+
+func TestDefaultManagerGetEncryptKey(t *testing.T) {
+	d := &defaultManager{}
+	k, err := d.getEncryptKey()
+	assert.Nil(t, err)
+	assert.Equal(t, "naa4JtarA1Zsc3uY", k)
+	d2 := &defaultManager{key: "oldkey"}
+	k2, err := d2.getEncryptKey()
+	assert.Nil(t, err)
+	assert.Equal(t, "oldkey", k2)
+}
+
+func TestPkgVerifySecret(t *testing.T) {
+	SetHardcodeVerifierForTest("secret")
+	assert.Nil(t, VerifySecret(context.Background(), 1, "secret"))
+	assert.NotNil(t, VerifySecret(context.Background(), 1, "not-the-secret"))
+}
diff --git a/src/common/utils/oidc/testutils.go b/src/common/utils/oidc/testutils.go
new file mode 100644
index 000000000..b8ceb624b
--- /dev/null
+++ b/src/common/utils/oidc/testutils.go
@@ -0,0 +1,26 @@
+package oidc
+
+import "context"
+import "errors"
+
+// This is for testing only
+type fakeVerifier struct {
+	secret string
+}
+
+func (fv *fakeVerifier) SetSecret(uid int, s string, t *Token) error {
+	return nil
+}
+
+func (fv *fakeVerifier) VerifySecret(ctx context.Context, userID int, secret string) error {
+	if secret != fv.secret {
+		return verifyError(errors.New("mismatch"))
+	}
+	return nil
+}
+
+// SetHardcodeVerifierForTest overwrite the default secret manager for testing.
+// Be reminded this is for testing only.
+func SetHardcodeVerifierForTest(s string) {
+	m = &fakeVerifier{s}
+}
diff --git a/src/core/api/admin_job.go b/src/core/api/admin_job.go
index c45941d5a..bc7cdbb16 100644
--- a/src/core/api/admin_job.go
+++ b/src/core/api/admin_job.go
@@ -27,6 +27,7 @@ import (
 	"github.com/goharbor/harbor/src/common/utils/log"
 	"github.com/goharbor/harbor/src/core/api/models"
 	utils_core "github.com/goharbor/harbor/src/core/utils"
+	"github.com/pkg/errors"
 )
 
 // AJAPI manages the CRUD of admin job and its schedule, any API wants to handle manual and cron job like ScanAll and GC cloud reuse it.
@@ -42,7 +43,7 @@ func (aj *AJAPI) Prepare() {
 // updateSchedule update a schedule of admin job.
 func (aj *AJAPI) updateSchedule(ajr models.AdminJobReq) {
 	if ajr.Schedule.Type == models.ScheduleManual {
-		aj.HandleInternalServerError(fmt.Sprintf("Fail to update admin job schedule as wrong schedule type: %s.", ajr.Schedule.Type))
+		aj.SendInternalServerError((fmt.Errorf("fail to update admin job schedule as wrong schedule type: %s", ajr.Schedule.Type)))
 		return
 	}
 
@@ -52,24 +53,24 @@ func (aj *AJAPI) updateSchedule(ajr models.AdminJobReq) {
 	}
 	jobs, err := dao.GetAdminJobs(query)
 	if err != nil {
-		aj.HandleInternalServerError(fmt.Sprintf("%v", err))
+		aj.SendInternalServerError(err)
 		return
 	}
 	if len(jobs) != 1 {
-		aj.HandleInternalServerError("Fail to update admin job schedule as we found more than one schedule in system, please ensure that only one schedule left for your job .")
+		aj.SendInternalServerError(errors.New("fail to update admin job schedule as we found more than one schedule in system, please ensure that only one schedule left for your job"))
 		return
 	}
 
 	// stop the scheduled job and remove it.
 	if err = utils_core.GetJobServiceClient().PostAction(jobs[0].UUID, common_job.JobActionStop); err != nil {
 		if e, ok := err.(*common_http.Error); !ok || e.Code != http.StatusNotFound {
-			aj.HandleInternalServerError(fmt.Sprintf("%v", err))
+			aj.SendInternalServerError(err)
 			return
 		}
 	}
 
 	if err = dao.DeleteAdminJob(jobs[0].ID); err != nil {
-		aj.HandleInternalServerError(fmt.Sprintf("%v", err))
+		aj.SendInternalServerError(err)
 		return
 	}
 
@@ -85,17 +86,17 @@ func (aj *AJAPI) get(id int64) {
 		ID: id,
 	})
 	if err != nil {
-		aj.HandleInternalServerError(fmt.Sprintf("failed to get admin jobs: %v", err))
+		aj.SendInternalServerError(fmt.Errorf("failed to get admin jobs: %v", err))
 		return
 	}
 	if len(jobs) == 0 {
-		aj.HandleNotFound("No admin job found.")
+		aj.SendNotFoundError(errors.New("no admin job found"))
 		return
 	}
 
 	adminJobRep, err := convertToAdminJobRep(jobs[0])
 	if err != nil {
-		aj.HandleInternalServerError(fmt.Sprintf("failed to convert admin job response: %v", err))
+		aj.SendInternalServerError(fmt.Errorf("failed to convert admin job response: %v", err))
 		return
 	}
 
@@ -107,7 +108,7 @@ func (aj *AJAPI) get(id int64) {
 func (aj *AJAPI) list(name string) {
 	jobs, err := dao.GetTop10AdminJobsOfName(name)
 	if err != nil {
-		aj.HandleInternalServerError(fmt.Sprintf("failed to get admin jobs: %v", err))
+		aj.SendInternalServerError(fmt.Errorf("failed to get admin jobs: %v", err))
 		return
 	}
 
@@ -115,7 +116,7 @@ func (aj *AJAPI) list(name string) {
 	for _, job := range jobs {
 		AdminJobRep, err := convertToAdminJobRep(job)
 		if err != nil {
-			aj.HandleInternalServerError(fmt.Sprintf("failed to convert admin job response: %v", err))
+			aj.SendInternalServerError(fmt.Errorf("failed to convert admin job response: %v", err))
 			return
 		}
 		AdminJobReps = append(AdminJobReps, &AdminJobRep)
@@ -134,18 +135,18 @@ func (aj *AJAPI) getSchedule(name string) {
 		Kind: common_job.JobKindPeriodic,
 	})
 	if err != nil {
-		aj.HandleInternalServerError(fmt.Sprintf("failed to get admin jobs: %v", err))
+		aj.SendInternalServerError(fmt.Errorf("failed to get admin jobs: %v", err))
 		return
 	}
 	if len(jobs) > 1 {
-		aj.HandleInternalServerError("Get more than one scheduled admin job, make sure there has only one.")
+		aj.SendInternalServerError(errors.New("get more than one scheduled admin job, make sure there has only one"))
 		return
 	}
 
 	if len(jobs) != 0 {
 		adminJobRep, err := convertToAdminJobRep(jobs[0])
 		if err != nil {
-			aj.HandleInternalServerError(fmt.Sprintf("failed to convert admin job response: %v", err))
+			aj.SendInternalServerError(fmt.Errorf("failed to convert admin job response: %v", err))
 			return
 		}
 		adminJobSchedule.Schedule = adminJobRep.Schedule
@@ -160,11 +161,13 @@ func (aj *AJAPI) getLog(id int64) {
 	job, err := dao.GetAdminJob(id)
 	if err != nil {
 		log.Errorf("Failed to load job data for job: %d, error: %v", id, err)
-		aj.CustomAbort(http.StatusInternalServerError, "Failed to get Job data")
+		aj.SendInternalServerError(errors.New("Failed to get Job data"))
+		return
 	}
 	if job == nil {
 		log.Errorf("Failed to get admin job: %d", id)
-		aj.CustomAbort(http.StatusNotFound, "Failed to get Job")
+		aj.SendNotFoundError(errors.New("Failed to get Job"))
+		return
 	}
 
 	logBytes, err := utils_core.GetJobServiceClient().GetJobLog(job.UUID)
@@ -175,14 +178,14 @@ func (aj *AJAPI) getLog(id int64) {
 				id, httpErr.Code, httpErr.Message))
 			return
 		}
-		aj.HandleInternalServerError(fmt.Sprintf("Failed to get job logs, uuid: %s, error: %v", job.UUID, err))
+		aj.SendInternalServerError(fmt.Errorf("Failed to get job logs, uuid: %s, error: %v", job.UUID, err))
 		return
 	}
 	aj.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Length"), strconv.Itoa(len(logBytes)))
 	aj.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Type"), "text/plain")
 	_, err = aj.Ctx.ResponseWriter.Write(logBytes)
 	if err != nil {
-		aj.HandleInternalServerError(fmt.Sprintf("Failed to write job logs, uuid: %s, error: %v", job.UUID, err))
+		aj.SendInternalServerError(fmt.Errorf("Failed to write job logs, uuid: %s, error: %v", job.UUID, err))
 	}
 }
 
@@ -195,11 +198,11 @@ func (aj *AJAPI) submit(ajr *models.AdminJobReq) {
 			Kind: common_job.JobKindPeriodic,
 		})
 		if err != nil {
-			aj.HandleInternalServerError(fmt.Sprintf("failed to get admin jobs: %v", err))
+			aj.SendInternalServerError(fmt.Errorf("failed to get admin jobs: %v", err))
 			return
 		}
 		if len(jobs) != 0 {
-			aj.HandleStatusPreconditionFailed("Fail to set schedule for admin job as always had one, please delete it firstly then to re-schedule.")
+			aj.SendPreconditionFailedError(errors.New("fail to set schedule for admin job as always had one, please delete it firstly then to re-schedule"))
 			return
 		}
 	}
@@ -210,7 +213,7 @@ func (aj *AJAPI) submit(ajr *models.AdminJobReq) {
 		Cron: ajr.CronString(),
 	})
 	if err != nil {
-		aj.HandleInternalServerError(fmt.Sprintf("%v", err))
+		aj.SendInternalServerError(err)
 		return
 	}
 	ajr.ID = id
@@ -224,14 +227,14 @@ func (aj *AJAPI) submit(ajr *models.AdminJobReq) {
 			log.Debugf("Failed to delete admin job, err: %v", err)
 		}
 		if httpErr, ok := err.(*common_http.Error); ok && httpErr.Code == http.StatusConflict {
-			aj.HandleConflict(fmt.Sprintf("Conflict when triggering %s, please try again later.", ajr.Name))
+			aj.SendConflictError(fmt.Errorf("conflict when triggering %s, please try again later", ajr.Name))
 			return
 		}
-		aj.HandleInternalServerError(fmt.Sprintf("%v", err))
+		aj.SendInternalServerError(err)
 		return
 	}
 	if err := dao.SetAdminJobUUID(id, uuid); err != nil {
-		aj.HandleInternalServerError(fmt.Sprintf("%v", err))
+		aj.SendInternalServerError(err)
 		return
 	}
 }
diff --git a/src/core/api/base.go b/src/core/api/base.go
index 22bc2c059..bea127d0b 100644
--- a/src/core/api/base.go
+++ b/src/core/api/base.go
@@ -17,14 +17,14 @@ package api
 import (
 	"net/http"
 
-	yaml "github.com/ghodss/yaml"
+	"errors"
+	"github.com/ghodss/yaml"
 	"github.com/goharbor/harbor/src/common/api"
 	"github.com/goharbor/harbor/src/common/security"
 	"github.com/goharbor/harbor/src/common/utils/log"
 	"github.com/goharbor/harbor/src/core/config"
 	"github.com/goharbor/harbor/src/core/filter"
 	"github.com/goharbor/harbor/src/core/promgr"
-	"github.com/goharbor/harbor/src/core/utils"
 )
 
 const (
@@ -54,55 +54,20 @@ func (b *BaseController) Prepare() {
 	ctx, err := filter.GetSecurityContext(b.Ctx.Request)
 	if err != nil {
 		log.Errorf("failed to get security context: %v", err)
-		b.CustomAbort(http.StatusInternalServerError, "")
+		b.SendInternalServerError(errors.New(""))
+		return
 	}
 	b.SecurityCtx = ctx
 
 	pm, err := filter.GetProjectManager(b.Ctx.Request)
 	if err != nil {
 		log.Errorf("failed to get project manager: %v", err)
-		b.CustomAbort(http.StatusInternalServerError, "")
+		b.SendInternalServerError(errors.New(""))
+		return
 	}
 	b.ProjectMgr = pm
 }
 
-// RenderFormatedError renders errors with well formted style `{"error": "This is an error"}`
-func (b *BaseController) RenderFormatedError(code int, err error) {
-	formatedErr := utils.WrapError(err)
-	log.Errorf("%s %s failed with error: %s", b.Ctx.Request.Method, b.Ctx.Request.URL.String(), formatedErr.Error())
-	b.RenderError(code, formatedErr.Error())
-}
-
-// SendUnAuthorizedError sends unauthorized error to the client.
-func (b *BaseController) SendUnAuthorizedError(err error) {
-	b.RenderFormatedError(http.StatusUnauthorized, err)
-}
-
-// SendConflictError sends conflict error to the client.
-func (b *BaseController) SendConflictError(err error) {
-	b.RenderFormatedError(http.StatusConflict, err)
-}
-
-// SendNotFoundError sends not found error to the client.
-func (b *BaseController) SendNotFoundError(err error) {
-	b.RenderFormatedError(http.StatusNotFound, err)
-}
-
-// SendBadRequestError sends bad request error to the client.
-func (b *BaseController) SendBadRequestError(err error) {
-	b.RenderFormatedError(http.StatusBadRequest, err)
-}
-
-// SendInternalServerError sends internal server error to the client.
-func (b *BaseController) SendInternalServerError(err error) {
-	b.RenderFormatedError(http.StatusInternalServerError, err)
-}
-
-// SendForbiddenError sends forbidden error to the client.
-func (b *BaseController) SendForbiddenError(err error) {
-	b.RenderFormatedError(http.StatusForbidden, err)
-}
-
 // WriteJSONData writes the JSON data to the client.
 func (b *BaseController) WriteJSONData(object interface{}) {
 	b.Data["json"] = object
diff --git a/src/core/api/chart_label.go b/src/core/api/chart_label.go
index f3de48c9c..02f5a98ee 100644
--- a/src/core/api/chart_label.go
+++ b/src/core/api/chart_label.go
@@ -61,7 +61,7 @@ func (cla *ChartLabelAPI) requireAccess(action rbac.Action) bool {
 	resource := rbac.NewProjectNamespace(cla.project.ProjectID).Resource(rbac.ResourceHelmChartVersionLabel)
 
 	if !cla.SecurityCtx.Can(action, resource) {
-		cla.HandleForbidden(cla.SecurityCtx.GetUsername())
+		cla.SendForbiddenError(errors.New(cla.SecurityCtx.GetUsername()))
 		return false
 	}
 
@@ -75,7 +75,10 @@ func (cla *ChartLabelAPI) MarkLabel() {
 	}
 
 	l := &models.Label{}
-	cla.DecodeJSONReq(l)
+	if err := cla.DecodeJSONReq(l); err != nil {
+		cla.SendBadRequestError(err)
+		return
+	}
 
 	label, ok := cla.validate(l.ID, cla.project.ProjectID)
 	if !ok {
diff --git a/src/core/api/chart_repository.go b/src/core/api/chart_repository.go
index 1af17b587..c7370ea85 100644
--- a/src/core/api/chart_repository.go
+++ b/src/core/api/chart_repository.go
@@ -96,7 +96,7 @@ func (cra *ChartRepositoryAPI) requireAccess(action rbac.Action, subresource ...
 		if !cra.SecurityCtx.IsAuthenticated() {
 			cra.SendUnAuthorizedError(errors.New("Unauthorized"))
 		} else {
-			cra.HandleForbidden(cra.SecurityCtx.GetUsername())
+			cra.SendForbiddenError(errors.New(cra.SecurityCtx.GetUsername()))
 		}
 
 		return false
@@ -114,7 +114,7 @@ func (cra *ChartRepositoryAPI) GetHealthStatus() {
 	}
 
 	if !cra.SecurityCtx.IsSysAdmin() {
-		cra.HandleForbidden(cra.SecurityCtx.GetUsername())
+		cra.SendForbiddenError(errors.New(cra.SecurityCtx.GetUsername()))
 		return
 	}
 
@@ -142,7 +142,7 @@ func (cra *ChartRepositoryAPI) GetIndex() {
 	}
 
 	if !cra.SecurityCtx.IsSysAdmin() {
-		cra.HandleForbidden(cra.SecurityCtx.GetUsername())
+		cra.SendForbiddenError(errors.New(cra.SecurityCtx.GetUsername()))
 		return
 	}
 
diff --git a/src/core/api/config.go b/src/core/api/config.go
index bc303d0d1..005385085 100644
--- a/src/core/api/config.go
+++ b/src/core/api/config.go
@@ -16,9 +16,9 @@ package api
 
 import (
 	"fmt"
-	"net/http"
 	"strings"
 
+	"errors"
 	"github.com/goharbor/harbor/src/common"
 	"github.com/goharbor/harbor/src/common/config"
 	"github.com/goharbor/harbor/src/common/config/metadata"
@@ -41,20 +41,20 @@ func (c *ConfigAPI) Prepare() {
 	c.BaseController.Prepare()
 	c.cfgManager = corecfg.GetCfgManager()
 	if !c.SecurityCtx.IsAuthenticated() {
-		c.HandleUnauthorized()
+		c.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 
 	// Only internal container can access /api/internal/configurations
 	if strings.EqualFold(c.Ctx.Request.RequestURI, "/api/internal/configurations") {
 		if _, ok := c.Ctx.Request.Context().Value(filter.SecurCtxKey).(*secret.SecurityContext); !ok {
-			c.HandleUnauthorized()
+			c.SendUnAuthorizedError(errors.New("UnAuthorized"))
 			return
 		}
 	}
 
 	if !c.SecurityCtx.IsSysAdmin() && !c.SecurityCtx.IsSolutionUser() {
-		c.HandleForbidden(c.SecurityCtx.GetUsername())
+		c.SendForbiddenError(errors.New(c.SecurityCtx.GetUsername()))
 		return
 	}
 
@@ -71,7 +71,8 @@ func (c *ConfigAPI) Get() {
 	m, err := convertForGet(configs)
 	if err != nil {
 		log.Errorf("failed to convert configurations: %v", err)
-		c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		c.SendInternalServerError(errors.New(""))
+		return
 	}
 
 	c.Data["json"] = m
@@ -89,25 +90,33 @@ func (c *ConfigAPI) GetInternalConfig() {
 // Put updates configurations
 func (c *ConfigAPI) Put() {
 	m := map[string]interface{}{}
-	c.DecodeJSONReq(&m)
+	if err := c.DecodeJSONReq(&m); err != nil {
+		c.SendBadRequestError(err)
+		return
+	}
 	err := c.cfgManager.Load()
 	if err != nil {
 		log.Errorf("failed to get configurations: %v", err)
-		c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		c.SendInternalServerError(errors.New(""))
+		return
 	}
 	isSysErr, err := c.validateCfg(m)
 	if err != nil {
 		if isSysErr {
 			log.Errorf("failed to validate configurations: %v", err)
-			c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+			c.SendInternalServerError(errors.New(""))
+			return
 		}
 
-		c.CustomAbort(http.StatusBadRequest, err.Error())
+		c.SendBadRequestError(err)
+		return
+
 	}
 
 	if err := c.cfgManager.UpdateConfig(m); err != nil {
 		log.Errorf("failed to upload configurations: %v", err)
-		c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		c.SendInternalServerError(errors.New(""))
+		return
 	}
 }
 
diff --git a/src/core/api/email.go b/src/core/api/email.go
index 3c7e056e2..9d21c428b 100644
--- a/src/core/api/email.go
+++ b/src/core/api/email.go
@@ -15,8 +15,8 @@
 package api
 
 import (
+	"errors"
 	"net"
-	"net/http"
 	"strconv"
 
 	"github.com/goharbor/harbor/src/common/utils/email"
@@ -37,12 +37,12 @@ type EmailAPI struct {
 func (e *EmailAPI) Prepare() {
 	e.BaseController.Prepare()
 	if !e.SecurityCtx.IsAuthenticated() {
-		e.HandleUnauthorized()
+		e.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 
 	if !e.SecurityCtx.IsSysAdmin() {
-		e.HandleForbidden(e.SecurityCtx.GetUsername())
+		e.SendForbiddenError(errors.New(e.SecurityCtx.GetUsername()))
 		return
 	}
 }
@@ -57,8 +57,8 @@ func (e *EmailAPI) Ping() {
 		cfg, err := config.Email()
 		if err != nil {
 			log.Errorf("failed to get email configurations: %v", err)
-			e.CustomAbort(http.StatusInternalServerError,
-				http.StatusText(http.StatusInternalServerError))
+			e.SendInternalServerError(err)
+			return
 		}
 		host = cfg.Host
 		port = cfg.Port
@@ -77,18 +77,22 @@ func (e *EmailAPI) Ping() {
 			Identity string  `json:"email_identity"`
 			Insecure bool    `json:"email_insecure"`
 		}{}
-		e.DecodeJSONReq(&settings)
+		if err := e.DecodeJSONReq(&settings); err != nil {
+			e.SendBadRequestError(err)
+			return
+		}
 
 		if len(settings.Host) == 0 || settings.Port == nil {
-			e.CustomAbort(http.StatusBadRequest, "empty host or port")
+			e.SendBadRequestError(errors.New("empty host or port"))
+			return
 		}
 
 		if settings.Password == nil {
 			cfg, err := config.Email()
 			if err != nil {
 				log.Errorf("failed to get email configurations: %v", err)
-				e.CustomAbort(http.StatusInternalServerError,
-					http.StatusText(http.StatusInternalServerError))
+				e.SendInternalServerError(err)
+				return
 			}
 
 			settings.Password = &cfg.Password
@@ -108,7 +112,7 @@ func (e *EmailAPI) Ping() {
 		password, pingEmailTimeout, ssl, insecure); err != nil {
 		log.Errorf("failed to ping email server: %v", err)
 		// do not return any detail information of the error, or may cause SSRF security issue #3755
-		e.RenderError(http.StatusBadRequest, "failed to ping email server")
+		e.SendBadRequestError(errors.New("failed to ping email server"))
 		return
 	}
 }
diff --git a/src/core/api/internal.go b/src/core/api/internal.go
index 9816b8955..71f1f317e 100644
--- a/src/core/api/internal.go
+++ b/src/core/api/internal.go
@@ -15,7 +15,7 @@
 package api
 
 import (
-	"net/http"
+	"errors"
 
 	"github.com/goharbor/harbor/src/common"
 	"github.com/goharbor/harbor/src/common/dao"
@@ -32,11 +32,11 @@ type InternalAPI struct {
 func (ia *InternalAPI) Prepare() {
 	ia.BaseController.Prepare()
 	if !ia.SecurityCtx.IsAuthenticated() {
-		ia.HandleUnauthorized()
+		ia.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 	if !ia.SecurityCtx.IsSysAdmin() {
-		ia.HandleForbidden(ia.SecurityCtx.GetUsername())
+		ia.SendForbiddenError(errors.New(ia.SecurityCtx.GetUsername()))
 		return
 	}
 }
@@ -45,7 +45,7 @@ func (ia *InternalAPI) Prepare() {
 func (ia *InternalAPI) SyncRegistry() {
 	err := SyncRegistry(ia.ProjectMgr)
 	if err != nil {
-		ia.HandleInternalServerError(err.Error())
+		ia.SendInternalServerError(err)
 		return
 	}
 }
@@ -54,7 +54,8 @@ func (ia *InternalAPI) SyncRegistry() {
 func (ia *InternalAPI) RenameAdmin() {
 	if !dao.IsSuperUser(ia.SecurityCtx.GetUsername()) {
 		log.Errorf("User %s is not super user, not allow to rename admin.", ia.SecurityCtx.GetUsername())
-		ia.CustomAbort(http.StatusForbidden, "")
+		ia.SendForbiddenError(errors.New(ia.SecurityCtx.GetUsername()))
+		return
 	}
 	newName := common.NewHarborAdminName
 	if err := dao.ChangeUserProfile(models.User{
@@ -62,7 +63,8 @@ func (ia *InternalAPI) RenameAdmin() {
 		Username: newName,
 	}, "username"); err != nil {
 		log.Errorf("Failed to change admin's username, error: %v", err)
-		ia.CustomAbort(http.StatusInternalServerError, "Failed to rename admin user.")
+		ia.SendInternalServerError(errors.New("failed to rename admin user"))
+		return
 	}
 	log.Debugf("The super user has been renamed to: %s", newName)
 	ia.DestroySession()
diff --git a/src/core/api/label.go b/src/core/api/label.go
index 8884e88c9..55a40c378 100644
--- a/src/core/api/label.go
+++ b/src/core/api/label.go
@@ -15,6 +15,7 @@
 package api
 
 import (
+	"errors"
 	"fmt"
 	"net/http"
 	"strconv"
@@ -41,25 +42,25 @@ func (l *LabelAPI) Prepare() {
 
 	// POST, PUT, DELETE need login first
 	if !l.SecurityCtx.IsAuthenticated() {
-		l.HandleUnauthorized()
+		l.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 
 	if method == http.MethodPut || method == http.MethodDelete {
 		id, err := l.GetInt64FromPath(":id")
 		if err != nil || id <= 0 {
-			l.HandleBadRequest("invalid label ID")
+			l.SendBadRequestError(errors.New("invalid lable ID"))
 			return
 		}
 
 		label, err := dao.GetLabel(id)
 		if err != nil {
-			l.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", id, err))
+			l.SendInternalServerError(fmt.Errorf("failed to get label %d: %v", id, err))
 			return
 		}
 
 		if label == nil || label.Deleted {
-			l.HandleNotFound(fmt.Sprintf("label %d not found", id))
+			l.SendNotFoundError(fmt.Errorf("label %d not found", id))
 			return
 		}
 
@@ -83,9 +84,9 @@ func (l *LabelAPI) requireAccess(label *models.Label, action rbac.Action, subres
 
 	if !hasPermission {
 		if !l.SecurityCtx.IsAuthenticated() {
-			l.HandleUnauthorized()
+			l.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		} else {
-			l.HandleForbidden(l.SecurityCtx.GetUsername())
+			l.SendForbiddenError(errors.New(l.SecurityCtx.GetUsername()))
 		}
 		return false
 	}
@@ -96,7 +97,12 @@ func (l *LabelAPI) requireAccess(label *models.Label, action rbac.Action, subres
 // Post creates a label
 func (l *LabelAPI) Post() {
 	label := &models.Label{}
-	l.DecodeJSONReqAndValidate(label)
+	isValid, err := l.DecodeJSONReqAndValidate(label)
+	if !isValid {
+		l.SendBadRequestError(err)
+		return
+	}
+
 	label.Level = common.LabelLevelUser
 
 	switch label.Scope {
@@ -105,12 +111,12 @@ func (l *LabelAPI) Post() {
 	case common.LabelScopeProject:
 		exist, err := l.ProjectMgr.Exists(label.ProjectID)
 		if err != nil {
-			l.HandleInternalServerError(fmt.Sprintf("failed to check the existence of project %d: %v",
+			l.SendInternalServerError(fmt.Errorf("failed to check the existence of project %d: %v",
 				label.ProjectID, err))
 			return
 		}
 		if !exist {
-			l.HandleNotFound(fmt.Sprintf("project %d not found", label.ProjectID))
+			l.SendNotFoundError(fmt.Errorf("project %d not found", label.ProjectID))
 			return
 		}
 	}
@@ -126,17 +132,17 @@ func (l *LabelAPI) Post() {
 		ProjectID: label.ProjectID,
 	})
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("failed to list labels: %v", err))
+		l.SendInternalServerError(fmt.Errorf("failed to list labels: %v", err))
 		return
 	}
 	if len(labels) > 0 {
-		l.HandleConflict()
+		l.SendConflictError(errors.New("conflict label"))
 		return
 	}
 
 	id, err := dao.AddLabel(label)
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("failed to create label: %v", err))
+		l.SendInternalServerError(fmt.Errorf("failed to create label: %v", err))
 		return
 	}
 
@@ -147,18 +153,18 @@ func (l *LabelAPI) Post() {
 func (l *LabelAPI) Get() {
 	id, err := l.GetInt64FromPath(":id")
 	if err != nil || id <= 0 {
-		l.HandleBadRequest(fmt.Sprintf("invalid label ID: %s", l.GetStringFromPath(":id")))
+		l.SendBadRequestError(fmt.Errorf("invalid label ID: %s", l.GetStringFromPath(":id")))
 		return
 	}
 
 	label, err := dao.GetLabel(id)
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", id, err))
+		l.SendInternalServerError(fmt.Errorf("failed to get label %d: %v", id, err))
 		return
 	}
 
 	if label == nil || label.Deleted {
-		l.HandleNotFound(fmt.Sprintf("label %d not found", id))
+		l.SendNotFoundError(fmt.Errorf("label %d not found", id))
 		return
 	}
 
@@ -180,7 +186,7 @@ func (l *LabelAPI) List() {
 
 	scope := l.GetString("scope")
 	if scope != common.LabelScopeGlobal && scope != common.LabelScopeProject {
-		l.HandleBadRequest(fmt.Sprintf("invalid scope: %s", scope))
+		l.SendBadRequestError(fmt.Errorf("invalid scope: %s", scope))
 		return
 	}
 	query.Scope = scope
@@ -188,22 +194,22 @@ func (l *LabelAPI) List() {
 	if scope == common.LabelScopeProject {
 		projectIDStr := l.GetString("project_id")
 		if len(projectIDStr) == 0 {
-			l.HandleBadRequest("project_id is required")
+			l.SendBadRequestError(errors.New("project_id is required"))
 			return
 		}
 		projectID, err := strconv.ParseInt(projectIDStr, 10, 64)
 		if err != nil || projectID <= 0 {
-			l.HandleBadRequest(fmt.Sprintf("invalid project_id: %s", projectIDStr))
+			l.SendBadRequestError(fmt.Errorf("invalid project_id: %s", projectIDStr))
 			return
 		}
 
 		resource := rbac.NewProjectNamespace(projectID).Resource(rbac.ResourceLabel)
 		if !l.SecurityCtx.Can(rbac.ActionList, resource) {
 			if !l.SecurityCtx.IsAuthenticated() {
-				l.HandleUnauthorized()
+				l.SendUnAuthorizedError(errors.New("UnAuthorized"))
 				return
 			}
-			l.HandleForbidden(l.SecurityCtx.GetUsername())
+			l.SendForbiddenError(errors.New(l.SecurityCtx.GetUsername()))
 			return
 		}
 		query.ProjectID = projectID
@@ -211,15 +217,19 @@ func (l *LabelAPI) List() {
 
 	total, err := dao.GetTotalOfLabels(query)
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("failed to get total count of labels: %v", err))
+		l.SendInternalServerError(fmt.Errorf("failed to get total count of labels: %v", err))
 		return
 	}
 
-	query.Page, query.Size = l.GetPaginationParams()
+	query.Page, query.Size, err = l.GetPaginationParams()
+	if err != nil {
+		l.SendBadRequestError(err)
+		return
+	}
 
 	labels, err := dao.ListLabels(query)
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("failed to list labels: %v", err))
+		l.SendInternalServerError(fmt.Errorf("failed to list labels: %v", err))
 		return
 	}
 
@@ -235,7 +245,10 @@ func (l *LabelAPI) Put() {
 	}
 
 	label := &models.Label{}
-	l.DecodeJSONReq(label)
+	if err := l.DecodeJSONReq(label); err != nil {
+		l.SendBadRequestError(err)
+		return
+	}
 
 	oldName := l.label.Name
 
@@ -244,7 +257,13 @@ func (l *LabelAPI) Put() {
 	l.label.Description = label.Description
 	l.label.Color = label.Color
 
-	l.Validate(l.label)
+	isValidate, err := l.Validate(l.label)
+	if !isValidate {
+		if err != nil {
+			l.SendBadRequestError(err)
+			return
+		}
+	}
 
 	if l.label.Name != oldName {
 		labels, err := dao.ListLabels(&models.LabelQuery{
@@ -254,17 +273,17 @@ func (l *LabelAPI) Put() {
 			ProjectID: l.label.ProjectID,
 		})
 		if err != nil {
-			l.HandleInternalServerError(fmt.Sprintf("failed to list labels: %v", err))
+			l.SendInternalServerError(fmt.Errorf("failed to list labels: %v", err))
 			return
 		}
 		if len(labels) > 0 {
-			l.HandleConflict()
+			l.SendConflictError(errors.New("conflict label"))
 			return
 		}
 	}
 
 	if err := dao.UpdateLabel(l.label); err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("failed to update label %d: %v", l.label.ID, err))
+		l.SendInternalServerError(fmt.Errorf("failed to update label %d: %v", l.label.ID, err))
 		return
 	}
 
@@ -278,11 +297,11 @@ func (l *LabelAPI) Delete() {
 
 	id := l.label.ID
 	if err := dao.DeleteResourceLabelByLabel(id); err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("failed to delete resource label mappings of label %d: %v", id, err))
+		l.SendInternalServerError(fmt.Errorf("failed to delete resource label mappings of label %d: %v", id, err))
 		return
 	}
 	if err := dao.DeleteLabel(id); err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("failed to delete label %d: %v", id, err))
+		l.SendInternalServerError(fmt.Errorf("failed to delete label %d: %v", id, err))
 		return
 	}
 }
@@ -291,18 +310,18 @@ func (l *LabelAPI) Delete() {
 func (l *LabelAPI) ListResources() {
 	id, err := l.GetInt64FromPath(":id")
 	if err != nil || id <= 0 {
-		l.HandleBadRequest("invalid label ID")
+		l.SendBadRequestError(errors.New("invalid label ID"))
 		return
 	}
 
 	label, err := dao.GetLabel(id)
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("failed to get label %d: %v", id, err))
+		l.SendInternalServerError(fmt.Errorf("failed to get label %d: %v", id, err))
 		return
 	}
 
 	if label == nil || label.Deleted {
-		l.HandleNotFound(fmt.Sprintf("label %d not found", id))
+		l.SendNotFoundError(fmt.Errorf("label %d not found", id))
 		return
 	}
 
diff --git a/src/core/api/ldap.go b/src/core/api/ldap.go
index 81f45dd87..a5dc182ca 100644
--- a/src/core/api/ldap.go
+++ b/src/core/api/ldap.go
@@ -22,6 +22,7 @@ import (
 	"github.com/goharbor/harbor/src/common/utils/log"
 	"github.com/goharbor/harbor/src/core/auth"
 
+	"errors"
 	goldap "gopkg.in/ldap.v2"
 )
 
@@ -43,17 +44,17 @@ const (
 func (l *LdapAPI) Prepare() {
 	l.BaseController.Prepare()
 	if !l.SecurityCtx.IsAuthenticated() {
-		l.HandleUnauthorized()
+		l.SendUnAuthorizedError(errors.New("Unauthorized"))
 		return
 	}
 	if !l.SecurityCtx.IsSysAdmin() {
-		l.HandleForbidden(l.SecurityCtx.GetUsername())
+		l.SendForbiddenError(errors.New(l.SecurityCtx.GetUsername()))
 		return
 	}
 
 	ldapCfg, err := ldapUtils.LoadSystemLdapConfig()
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("Can't load system configuration, error: %v", err))
+		l.SendInternalServerError(fmt.Errorf("Can't load system configuration, error: %v", err))
 		return
 	}
 	l.ldapConfig = ldapCfg
@@ -73,12 +74,16 @@ func (l *LdapAPI) Ping() {
 		ldapSession := *l.ldapConfig
 		err = ldapSession.ConnectionTest()
 	} else {
-		l.DecodeJSONReqAndValidate(&ldapConfs)
+		isValid, err := l.DecodeJSONReqAndValidate(&ldapConfs)
+		if !isValid {
+			l.SendBadRequestError(err)
+			return
+		}
 		err = ldapUtils.ConnectionTestWithConfig(ldapConfs)
 	}
 
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("LDAP connect fail, error: %v", err))
+		l.SendInternalServerError(fmt.Errorf("LDAP connect fail, error: %v", err))
 		return
 	}
 }
@@ -89,7 +94,7 @@ func (l *LdapAPI) Search() {
 	var ldapUsers []models.LdapUser
 	ldapSession := *l.ldapConfig
 	if err = ldapSession.Open(); err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("Can't Open LDAP session, error: %v", err))
+		l.SendInternalServerError(fmt.Errorf("can't Open LDAP session, error: %v", err))
 		return
 	}
 	defer ldapSession.Close()
@@ -99,7 +104,7 @@ func (l *LdapAPI) Search() {
 	ldapUsers, err = ldapSession.SearchUser(searchName)
 
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("LDAP search fail, error: %v", err))
+		l.SendInternalServerError(fmt.Errorf("LDAP search fail, error: %v", err))
 		return
 	}
 
@@ -113,18 +118,22 @@ func (l *LdapAPI) ImportUser() {
 	var ldapImportUsers models.LdapImportUser
 	var ldapFailedImportUsers []models.LdapFailedImportUser
 
-	l.DecodeJSONReqAndValidate(&ldapImportUsers)
+	isValid, err := l.DecodeJSONReqAndValidate(&ldapImportUsers)
+	if !isValid {
+		l.SendBadRequestError(err)
+		return
+	}
 
-	ldapFailedImportUsers, err := importUsers(ldapImportUsers.LdapUIDList, l.ldapConfig)
+	ldapFailedImportUsers, err = importUsers(ldapImportUsers.LdapUIDList, l.ldapConfig)
 
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf("LDAP import user fail, error: %v", err))
+		l.SendInternalServerError(fmt.Errorf("LDAP import user fail, error: %v", err))
 		return
 	}
 
 	if len(ldapFailedImportUsers) > 0 {
 		// Some user require json format response.
-		l.HandleNotFound("")
+		l.SendNotFoundError(errors.New("ldap user is not found"))
 		l.Data["json"] = ldapFailedImportUsers
 		l.ServeJSON()
 		return
@@ -206,23 +215,23 @@ func (l *LdapAPI) SearchGroup() {
 	if len(searchName) > 0 {
 		ldapGroups, err = ldapSession.SearchGroupByName(searchName)
 		if err != nil {
-			l.HandleInternalServerError(fmt.Sprintf("Can't search LDAP group by name, error: %v", err))
+			l.SendInternalServerError(fmt.Errorf("can't search LDAP group by name, error: %v", err))
 			return
 		}
 	} else if len(groupDN) > 0 {
 		if _, err := goldap.ParseDN(groupDN); err != nil {
-			l.HandleBadRequest(fmt.Sprintf("Invalid DN: %v", err))
+			l.SendBadRequestError(fmt.Errorf("invalid DN: %v", err))
 			return
 		}
 		ldapGroups, err = ldapSession.SearchGroupByDN(groupDN)
 		if err != nil {
 			// OpenLDAP usually return an error if DN is not found
-			l.HandleNotFound(fmt.Sprintf("Search LDAP group fail, error: %v", err))
+			l.SendNotFoundError(fmt.Errorf("search LDAP group fail, error: %v", err))
 			return
 		}
 	}
 	if len(ldapGroups) == 0 {
-		l.HandleNotFound("No ldap group found")
+		l.SendNotFoundError(errors.New("No ldap group found"))
 		return
 	}
 	l.Data["json"] = ldapGroups
diff --git a/src/core/api/log.go b/src/core/api/log.go
index 718e37df7..54c427180 100644
--- a/src/core/api/log.go
+++ b/src/core/api/log.go
@@ -17,6 +17,7 @@ package api
 import (
 	"fmt"
 
+	"errors"
 	"github.com/goharbor/harbor/src/common/dao"
 	"github.com/goharbor/harbor/src/common/models"
 	"github.com/goharbor/harbor/src/common/utils"
@@ -33,7 +34,7 @@ type LogAPI struct {
 func (l *LogAPI) Prepare() {
 	l.BaseController.Prepare()
 	if !l.SecurityCtx.IsAuthenticated() {
-		l.HandleUnauthorized()
+		l.SendUnAuthorizedError(errors.New("Unauthorized"))
 		return
 	}
 	l.username = l.SecurityCtx.GetUsername()
@@ -42,7 +43,11 @@ func (l *LogAPI) Prepare() {
 
 // Get returns the recent logs according to parameters
 func (l *LogAPI) Get() {
-	page, size := l.GetPaginationParams()
+	page, size, err := l.GetPaginationParams()
+	if err != nil {
+		l.SendBadRequestError(err)
+		return
+	}
 	query := &models.LogQueryParam{
 		Username:   l.GetString("username"),
 		Repository: l.GetString("repository"),
@@ -58,7 +63,7 @@ func (l *LogAPI) Get() {
 	if len(timestamp) > 0 {
 		t, err := utils.ParseTimeStamp(timestamp)
 		if err != nil {
-			l.HandleBadRequest(fmt.Sprintf("invalid begin_timestamp: %s", timestamp))
+			l.SendBadRequestError(fmt.Errorf("invalid begin_timestamp: %s", timestamp))
 			return
 		}
 		query.BeginTime = t
@@ -68,7 +73,7 @@ func (l *LogAPI) Get() {
 	if len(timestamp) > 0 {
 		t, err := utils.ParseTimeStamp(timestamp)
 		if err != nil {
-			l.HandleBadRequest(fmt.Sprintf("invalid end_timestamp: %s", timestamp))
+			l.SendBadRequestError(fmt.Errorf("invalid end_timestamp: %s", timestamp))
 			return
 		}
 		query.EndTime = t
@@ -77,7 +82,7 @@ func (l *LogAPI) Get() {
 	if !l.isSysAdmin {
 		projects, err := l.SecurityCtx.GetMyProjects()
 		if err != nil {
-			l.HandleInternalServerError(fmt.Sprintf(
+			l.SendInternalServerError(fmt.Errorf(
 				"failed to get projects of user %s: %v", l.username, err))
 			return
 		}
@@ -98,14 +103,14 @@ func (l *LogAPI) Get() {
 
 	total, err := dao.GetTotalOfAccessLogs(query)
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf(
+		l.SendInternalServerError(fmt.Errorf(
 			"failed to get total of access logs: %v", err))
 		return
 	}
 
 	logs, err := dao.GetAccessLogs(query)
 	if err != nil {
-		l.HandleInternalServerError(fmt.Sprintf(
+		l.SendInternalServerError(fmt.Errorf(
 			"failed to get access logs: %v", err))
 		return
 	}
diff --git a/src/core/api/metadata.go b/src/core/api/metadata.go
index 146d6de09..5dd34b8c0 100644
--- a/src/core/api/metadata.go
+++ b/src/core/api/metadata.go
@@ -21,6 +21,7 @@ import (
 	"strconv"
 	"strings"
 
+	"errors"
 	"github.com/goharbor/harbor/src/common/models"
 	"github.com/goharbor/harbor/src/common/rbac"
 	"github.com/goharbor/harbor/src/common/utils/log"
@@ -56,7 +57,7 @@ func (m *MetadataAPI) Prepare() {
 		} else {
 			text += fmt.Sprintf("%d", id)
 		}
-		m.HandleBadRequest(text)
+		m.SendBadRequestError(errors.New(text))
 		return
 	}
 
@@ -67,7 +68,7 @@ func (m *MetadataAPI) Prepare() {
 	}
 
 	if project == nil {
-		m.HandleNotFound(fmt.Sprintf("project %d not found", id))
+		m.SendNotFoundError(fmt.Errorf("project %d not found", id))
 		return
 	}
 
@@ -78,11 +79,11 @@ func (m *MetadataAPI) Prepare() {
 		m.name = name
 		metas, err := m.metaMgr.Get(project.ProjectID, name)
 		if err != nil {
-			m.HandleInternalServerError(fmt.Sprintf("failed to get metadata of project %d: %v", project.ProjectID, err))
+			m.SendInternalServerError(fmt.Errorf("failed to get metadata of project %d: %v", project.ProjectID, err))
 			return
 		}
 		if len(metas) == 0 {
-			m.HandleNotFound(fmt.Sprintf("metadata %s of project %d not found", name, project.ProjectID))
+			m.SendNotFoundError(fmt.Errorf("metadata %s of project %d not found", name, project.ProjectID))
 			return
 		}
 	}
@@ -93,9 +94,9 @@ func (m *MetadataAPI) requireAccess(action rbac.Action) bool {
 
 	if !m.SecurityCtx.Can(action, resource) {
 		if !m.SecurityCtx.IsAuthenticated() {
-			m.HandleUnauthorized()
+			m.SendUnAuthorizedError(errors.New("Unauthorized"))
 		} else {
-			m.HandleForbidden(m.SecurityCtx.GetUsername())
+			m.SendForbiddenError(errors.New(m.SecurityCtx.GetUsername()))
 		}
 		return false
 	}
@@ -118,7 +119,7 @@ func (m *MetadataAPI) Get() {
 	}
 
 	if err != nil {
-		m.HandleInternalServerError(fmt.Sprintf("failed to get metadata %s of project %d: %v", m.name, m.project.ProjectID, err))
+		m.SendInternalServerError(fmt.Errorf("failed to get metadata %s of project %d: %v", m.name, m.project.ProjectID, err))
 		return
 	}
 	m.Data["json"] = metas
@@ -132,33 +133,36 @@ func (m *MetadataAPI) Post() {
 	}
 
 	var metas map[string]string
-	m.DecodeJSONReq(&metas)
+	if err := m.DecodeJSONReq(&metas); err != nil {
+		m.SendBadRequestError(err)
+		return
+	}
 
 	ms, err := validateProjectMetadata(metas)
 	if err != nil {
-		m.HandleBadRequest(err.Error())
+		m.SendBadRequestError(err)
 		return
 	}
 
 	if len(ms) != 1 {
-		m.HandleBadRequest("invalid request: has no valid key/value pairs or has more than one valid key/value pairs")
+		m.SendBadRequestError(errors.New("invalid request: has no valid key/value pairs or has more than one valid key/value pairs"))
 		return
 	}
 
 	keys := reflect.ValueOf(ms).MapKeys()
 	mts, err := m.metaMgr.Get(m.project.ProjectID, keys[0].String())
 	if err != nil {
-		m.HandleInternalServerError(fmt.Sprintf("failed to get metadata for project %d: %v", m.project.ProjectID, err))
+		m.SendInternalServerError(fmt.Errorf("failed to get metadata for project %d: %v", m.project.ProjectID, err))
 		return
 	}
 
 	if len(mts) != 0 {
-		m.HandleConflict()
+		m.SendConflictError(errors.New("conflict metadata"))
 		return
 	}
 
 	if err := m.metaMgr.Add(m.project.ProjectID, ms); err != nil {
-		m.HandleInternalServerError(fmt.Sprintf("failed to create metadata for project %d: %v", m.project.ProjectID, err))
+		m.SendInternalServerError(fmt.Errorf("failed to create metadata for project %d: %v", m.project.ProjectID, err))
 		return
 	}
 
@@ -172,11 +176,14 @@ func (m *MetadataAPI) Put() {
 	}
 
 	var metas map[string]string
-	m.DecodeJSONReq(&metas)
+	if err := m.DecodeJSONReq(&metas); err != nil {
+		m.SendBadRequestError(err)
+		return
+	}
 
 	meta, exist := metas[m.name]
 	if !exist {
-		m.HandleBadRequest(fmt.Sprintf("must contains key %s", m.name))
+		m.SendBadRequestError(fmt.Errorf("must contains key %s", m.name))
 		return
 	}
 
@@ -184,14 +191,14 @@ func (m *MetadataAPI) Put() {
 		m.name: meta,
 	})
 	if err != nil {
-		m.HandleBadRequest(err.Error())
+		m.SendBadRequestError(err)
 		return
 	}
 
 	if err := m.metaMgr.Update(m.project.ProjectID, map[string]string{
 		m.name: ms[m.name],
 	}); err != nil {
-		m.HandleInternalServerError(fmt.Sprintf("failed to update metadata %s of project %d: %v", m.name, m.project.ProjectID, err))
+		m.SendInternalServerError(fmt.Errorf("failed to update metadata %s of project %d: %v", m.name, m.project.ProjectID, err))
 		return
 	}
 }
@@ -203,7 +210,7 @@ func (m *MetadataAPI) Delete() {
 	}
 
 	if err := m.metaMgr.Delete(m.project.ProjectID, m.name); err != nil {
-		m.HandleInternalServerError(fmt.Sprintf("failed to delete metadata %s of project %d: %v", m.name, m.project.ProjectID, err))
+		m.SendInternalServerError(fmt.Errorf("failed to delete metadata %s of project %d: %v", m.name, m.project.ProjectID, err))
 		return
 	}
 }
diff --git a/src/core/api/project.go b/src/core/api/project.go
index 31b0b4380..aaf0f2e02 100644
--- a/src/core/api/project.go
+++ b/src/core/api/project.go
@@ -28,6 +28,7 @@ import (
 	"github.com/goharbor/harbor/src/common/utils/log"
 	"github.com/goharbor/harbor/src/core/config"
 
+	"errors"
 	"strconv"
 	"time"
 )
@@ -59,7 +60,7 @@ func (p *ProjectAPI) Prepare() {
 			} else {
 				text += fmt.Sprintf("%d", id)
 			}
-			p.HandleBadRequest(text)
+			p.SendBadRequestError(errors.New(text))
 			return
 		}
 
@@ -70,7 +71,7 @@ func (p *ProjectAPI) Prepare() {
 		}
 
 		if project == nil {
-			p.HandleNotFound(fmt.Sprintf("project %d not found", id))
+			p.SendNotFoundError(fmt.Errorf("project %d not found", id))
 			return
 		}
 
@@ -86,9 +87,10 @@ func (p *ProjectAPI) requireAccess(action rbac.Action, subresource ...rbac.Resou
 
 	if !p.SecurityCtx.Can(action, resource) {
 		if !p.SecurityCtx.IsAuthenticated() {
-			p.HandleUnauthorized()
+			p.SendUnAuthorizedError(errors.New("Unauthorized"))
+
 		} else {
-			p.HandleForbidden(p.SecurityCtx.GetUsername())
+			p.SendForbiddenError(errors.New(p.SecurityCtx.GetUsername()))
 		}
 
 		return false
@@ -100,7 +102,7 @@ func (p *ProjectAPI) requireAccess(action rbac.Action, subresource ...rbac.Resou
 // Post ...
 func (p *ProjectAPI) Post() {
 	if !p.SecurityCtx.IsAuthenticated() {
-		p.HandleUnauthorized()
+		p.SendUnAuthorizedError(errors.New("Unauthorized"))
 		return
 	}
 	var onlyAdmin bool
@@ -111,21 +113,25 @@ func (p *ProjectAPI) Post() {
 		onlyAdmin, err = config.OnlyAdminCreateProject()
 		if err != nil {
 			log.Errorf("failed to determine whether only admin can create projects: %v", err)
-			p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+			p.SendInternalServerError(fmt.Errorf("failed to determine whether only admin can create projects: %v", err))
+			return
 		}
 	}
 
 	if onlyAdmin && !(p.SecurityCtx.IsSysAdmin() || p.SecurityCtx.IsSolutionUser()) {
 		log.Errorf("Only sys admin can create project")
-		p.RenderError(http.StatusForbidden, "Only system admin can create project")
+		p.SendForbiddenError(errors.New("Only system admin can create project"))
 		return
 	}
 	var pro *models.ProjectRequest
-	p.DecodeJSONReq(&pro)
+	if err := p.DecodeJSONReq(&pro); err != nil {
+		p.SendBadRequestError(err)
+		return
+	}
 	err = validateProjectReq(pro)
 	if err != nil {
 		log.Errorf("Invalid project request, error: %v", err)
-		p.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid request: %v", err))
+		p.SendBadRequestError(fmt.Errorf("invalid request: %v", err))
 		return
 	}
 
@@ -136,7 +142,7 @@ func (p *ProjectAPI) Post() {
 		return
 	}
 	if exist {
-		p.RenderError(http.StatusConflict, "")
+		p.SendConflictError(errors.New("conflict project"))
 		return
 	}
 
@@ -162,7 +168,7 @@ func (p *ProjectAPI) Post() {
 			UserID: 1,
 		})
 		if err != nil {
-			p.HandleInternalServerError(fmt.Sprintf("failed to get the user 1: %v", err))
+			p.SendInternalServerError(fmt.Errorf("failed to get the user 1: %v", err))
 			return
 		}
 		owner = user.Username
@@ -175,7 +181,7 @@ func (p *ProjectAPI) Post() {
 	if err != nil {
 		if err == errutil.ErrDupProject {
 			log.Debugf("conflict %s", pro.Name)
-			p.RenderError(http.StatusConflict, "")
+			p.SendConflictError(fmt.Errorf("conflict %s", pro.Name))
 		} else {
 			p.ParseAndHandleError("failed to add project", err)
 		}
@@ -203,7 +209,7 @@ func (p *ProjectAPI) Post() {
 func (p *ProjectAPI) Head() {
 	name := p.GetString("project_name")
 	if len(name) == 0 {
-		p.HandleBadRequest("project_name is needed")
+		p.SendBadRequestError(errors.New("project_name is needed"))
 		return
 	}
 
@@ -214,7 +220,7 @@ func (p *ProjectAPI) Head() {
 	}
 
 	if project == nil {
-		p.HandleNotFound(fmt.Sprintf("project %s not found", name))
+		p.SendNotFoundError(fmt.Errorf("project %s not found", name))
 		return
 	}
 }
@@ -239,12 +245,13 @@ func (p *ProjectAPI) Delete() {
 
 	result, err := p.deletable(p.project.ProjectID)
 	if err != nil {
-		p.HandleInternalServerError(fmt.Sprintf(
+		p.SendInternalServerError(fmt.Errorf(
 			"failed to check the deletable of project %d: %v", p.project.ProjectID, err))
 		return
 	}
 	if !result.Deletable {
-		p.CustomAbort(http.StatusPreconditionFailed, result.Message)
+		p.SendPreconditionFailedError(errors.New(result.Message))
+		return
 	}
 
 	if err = p.ProjectMgr.Delete(p.project.ProjectID); err != nil {
@@ -274,7 +281,7 @@ func (p *ProjectAPI) Deletable() {
 
 	result, err := p.deletable(p.project.ProjectID)
 	if err != nil {
-		p.HandleInternalServerError(fmt.Sprintf(
+		p.SendInternalServerError(fmt.Errorf(
 			"failed to check the deletable of project %d: %v", p.project.ProjectID, err))
 		return
 	}
@@ -321,7 +328,11 @@ func (p *ProjectAPI) deletable(projectID int64) (*deletableResp, error) {
 // List ...
 func (p *ProjectAPI) List() {
 	// query strings
-	page, size := p.GetPaginationParams()
+	page, size, err := p.GetPaginationParams()
+	if err != nil {
+		p.SendBadRequestError(err)
+		return
+	}
 	query := &models.ProjectQueryParam{
 		Name:  p.GetString("name"),
 		Owner: p.GetString("owner"),
@@ -335,7 +346,7 @@ func (p *ProjectAPI) List() {
 	if len(public) > 0 {
 		pub, err := strconv.ParseBool(public)
 		if err != nil {
-			p.HandleBadRequest(fmt.Sprintf("invalid public: %s", public))
+			p.SendBadRequestError(fmt.Errorf("invalid public: %s", public))
 			return
 		}
 		query.Public = &pub
@@ -348,7 +359,7 @@ func (p *ProjectAPI) List() {
 			// not login, only get public projects
 			pros, err := p.ProjectMgr.GetPublic()
 			if err != nil {
-				p.HandleInternalServerError(fmt.Sprintf("failed to get public projects: %v", err))
+				p.SendInternalServerError(fmt.Errorf("failed to get public projects: %v", err))
 				return
 			}
 			projects = []*models.Project{}
@@ -360,13 +371,13 @@ func (p *ProjectAPI) List() {
 				// projects that the user is member of
 				pros, err := p.ProjectMgr.GetPublic()
 				if err != nil {
-					p.HandleInternalServerError(fmt.Sprintf("failed to get public projects: %v", err))
+					p.SendInternalServerError(fmt.Errorf("failed to get public projects: %v", err))
 					return
 				}
 				projects = append(projects, pros...)
 				mps, err := p.SecurityCtx.GetMyProjects()
 				if err != nil {
-					p.HandleInternalServerError(fmt.Sprintf("failed to list projects: %v", err))
+					p.SendInternalServerError(fmt.Errorf("failed to list projects: %v", err))
 					return
 				}
 				projects = append(projects, mps...)
@@ -416,7 +427,8 @@ func (p *ProjectAPI) populateProperties(project *models.Project) {
 	})
 	if err != nil {
 		log.Errorf("failed to get total of repositories of project %d: %v", project.ProjectID, err)
-		p.CustomAbort(http.StatusInternalServerError, "")
+		p.SendInternalServerError(errors.New(""))
+		return
 	}
 
 	project.RepoCount = total
@@ -426,7 +438,8 @@ func (p *ProjectAPI) populateProperties(project *models.Project) {
 		count, err := chartController.GetCountOfCharts([]string{project.Name})
 		if err != nil {
 			log.Errorf("Failed to get total of charts under project %s: %v", project.Name, err)
-			p.CustomAbort(http.StatusInternalServerError, "")
+			p.SendInternalServerError(errors.New(""))
+			return
 		}
 
 		project.ChartCount = count
@@ -440,7 +453,10 @@ func (p *ProjectAPI) Put() {
 	}
 
 	var req *models.ProjectRequest
-	p.DecodeJSONReq(&req)
+	if err := p.DecodeJSONReq(&req); err != nil {
+		p.SendBadRequestError(err)
+		return
+	}
 
 	if err := p.ProjectMgr.Update(p.project.ProjectID,
 		&models.Project{
@@ -458,7 +474,11 @@ func (p *ProjectAPI) Logs() {
 		return
 	}
 
-	page, size := p.GetPaginationParams()
+	page, size, err := p.GetPaginationParams()
+	if err != nil {
+		p.SendBadRequestError(err)
+		return
+	}
 	query := &models.LogQueryParam{
 		ProjectIDs: []int64{p.project.ProjectID},
 		Username:   p.GetString("username"),
@@ -475,7 +495,7 @@ func (p *ProjectAPI) Logs() {
 	if len(timestamp) > 0 {
 		t, err := utils.ParseTimeStamp(timestamp)
 		if err != nil {
-			p.HandleBadRequest(fmt.Sprintf("invalid begin_timestamp: %s", timestamp))
+			p.SendBadRequestError(fmt.Errorf("invalid begin_timestamp: %s", timestamp))
 			return
 		}
 		query.BeginTime = t
@@ -485,7 +505,7 @@ func (p *ProjectAPI) Logs() {
 	if len(timestamp) > 0 {
 		t, err := utils.ParseTimeStamp(timestamp)
 		if err != nil {
-			p.HandleBadRequest(fmt.Sprintf("invalid end_timestamp: %s", timestamp))
+			p.SendBadRequestError(fmt.Errorf("invalid end_timestamp: %s", timestamp))
 			return
 		}
 		query.EndTime = t
@@ -493,14 +513,14 @@ func (p *ProjectAPI) Logs() {
 
 	total, err := dao.GetTotalOfAccessLogs(query)
 	if err != nil {
-		p.HandleInternalServerError(fmt.Sprintf(
+		p.SendInternalServerError(fmt.Errorf(
 			"failed to get total of access log: %v", err))
 		return
 	}
 
 	logs, err := dao.GetAccessLogs(query)
 	if err != nil {
-		p.HandleInternalServerError(fmt.Sprintf(
+		p.SendInternalServerError(fmt.Errorf(
 			"failed to get access log: %v", err))
 		return
 	}
diff --git a/src/core/api/projectmember.go b/src/core/api/projectmember.go
index 27fc4252d..0107a0d81 100644
--- a/src/core/api/projectmember.go
+++ b/src/core/api/projectmember.go
@@ -50,7 +50,7 @@ func (pma *ProjectMemberAPI) Prepare() {
 	pma.BaseController.Prepare()
 
 	if !pma.SecurityCtx.IsAuthenticated() {
-		pma.HandleUnauthorized()
+		pma.SendUnAuthorizedError(errors.New("Unauthorized"))
 		return
 	}
 	pid, err := pma.GetInt64FromPath(":pid")
@@ -61,7 +61,7 @@ func (pma *ProjectMemberAPI) Prepare() {
 		} else {
 			text += fmt.Sprintf("%d", pid)
 		}
-		pma.HandleBadRequest(text)
+		pma.SendBadRequestError(errors.New(text))
 		return
 	}
 	project, err := pma.ProjectMgr.Get(pid)
@@ -70,7 +70,7 @@ func (pma *ProjectMemberAPI) Prepare() {
 		return
 	}
 	if project == nil {
-		pma.HandleNotFound(fmt.Sprintf("project %d not found", pid))
+		pma.SendNotFoundError(fmt.Errorf("project %d not found", pid))
 		return
 	}
 	pma.project = project
@@ -80,7 +80,7 @@ func (pma *ProjectMemberAPI) Prepare() {
 		log.Warningf("Failed to get pmid from path, error %v", err)
 	}
 	if pmid <= 0 && (pma.Ctx.Input.IsPut() || pma.Ctx.Input.IsDelete()) {
-		pma.HandleBadRequest(fmt.Sprintf("The project member id is invalid, pmid:%s", pma.GetStringFromPath(":pmid")))
+		pma.SendBadRequestError(fmt.Errorf("The project member id is invalid, pmid:%s", pma.GetStringFromPath(":pmid")))
 		return
 	}
 	pma.id = int(pmid)
@@ -91,9 +91,9 @@ func (pma *ProjectMemberAPI) requireAccess(action rbac.Action) bool {
 
 	if !pma.SecurityCtx.Can(action, resource) {
 		if !pma.SecurityCtx.IsAuthenticated() {
-			pma.HandleUnauthorized()
+			pma.SendUnAuthorizedError(errors.New("Unauthorized"))
 		} else {
-			pma.HandleForbidden(pma.SecurityCtx.GetUsername())
+			pma.SendForbiddenError(errors.New(pma.SecurityCtx.GetUsername()))
 		}
 
 		return false
@@ -115,7 +115,7 @@ func (pma *ProjectMemberAPI) Get() {
 		entityname := pma.GetString("entityname")
 		memberList, err := project.SearchMemberByName(projectID, entityname)
 		if err != nil {
-			pma.HandleInternalServerError(fmt.Sprintf("Failed to query database for member list, error: %v", err))
+			pma.SendInternalServerError(fmt.Errorf("Failed to query database for member list, error: %v", err))
 			return
 		}
 		if len(memberList) > 0 {
@@ -127,11 +127,11 @@ func (pma *ProjectMemberAPI) Get() {
 		queryMember.ID = pma.id
 		memberList, err := project.GetProjectMember(queryMember)
 		if err != nil {
-			pma.HandleInternalServerError(fmt.Sprintf("Failed to query database for member list, error: %v", err))
+			pma.SendInternalServerError(fmt.Errorf("Failed to query database for member list, error: %v", err))
 			return
 		}
 		if len(memberList) == 0 {
-			pma.HandleNotFound(fmt.Sprintf("The project member does not exit, pmid:%v", pma.id))
+			pma.SendNotFoundError(fmt.Errorf("The project member does not exit, pmid:%v", pma.id))
 			return
 		}
 
@@ -150,27 +150,30 @@ func (pma *ProjectMemberAPI) Post() {
 	}
 	projectID := pma.project.ProjectID
 	var request models.MemberReq
-	pma.DecodeJSONReq(&request)
+	if err := pma.DecodeJSONReq(&request); err != nil {
+		pma.SendBadRequestError(err)
+		return
+	}
 	request.MemberGroup.LdapGroupDN = strings.TrimSpace(request.MemberGroup.LdapGroupDN)
 
 	pmid, err := AddProjectMember(projectID, request)
 	if err == auth.ErrorGroupNotExist || err == auth.ErrorUserNotExist {
-		pma.HandleNotFound(fmt.Sprintf("Failed to add project member, error: %v", err))
+		pma.SendNotFoundError(fmt.Errorf("Failed to add project member, error: %v", err))
 		return
 	} else if err == auth.ErrDuplicateLDAPGroup {
-		pma.HandleConflict(fmt.Sprintf("Failed to add project member, already exist LDAP group or project member, groupDN:%v", request.MemberGroup.LdapGroupDN))
+		pma.SendConflictError(fmt.Errorf("Failed to add project member, already exist LDAP group or project member, groupDN:%v", request.MemberGroup.LdapGroupDN))
 		return
 	} else if err == ErrDuplicateProjectMember {
-		pma.HandleConflict(fmt.Sprintf("Failed to add project member, already exist LDAP group or project member, groupMemberID:%v", request.MemberGroup.ID))
+		pma.SendConflictError(fmt.Errorf("Failed to add project member, already exist LDAP group or project member, groupMemberID:%v", request.MemberGroup.ID))
 		return
 	} else if err == ErrInvalidRole {
-		pma.HandleBadRequest(fmt.Sprintf("Invalid role ID, role ID %v", request.Role))
+		pma.SendBadRequestError(fmt.Errorf("Invalid role ID, role ID %v", request.Role))
 		return
 	} else if err == auth.ErrInvalidLDAPGroupDN {
-		pma.HandleBadRequest(fmt.Sprintf("Invalid LDAP DN: %v", request.MemberGroup.LdapGroupDN))
+		pma.SendBadRequestError(fmt.Errorf("Invalid LDAP DN: %v", request.MemberGroup.LdapGroupDN))
 		return
 	} else if err != nil {
-		pma.HandleInternalServerError(fmt.Sprintf("Failed to add project member, error: %v", err))
+		pma.SendInternalServerError(fmt.Errorf("Failed to add project member, error: %v", err))
 		return
 	}
 	pma.Redirect(http.StatusCreated, strconv.FormatInt(int64(pmid), 10))
@@ -184,14 +187,17 @@ func (pma *ProjectMemberAPI) Put() {
 	pid := pma.project.ProjectID
 	pmID := pma.id
 	var req models.Member
-	pma.DecodeJSONReq(&req)
+	if err := pma.DecodeJSONReq(&req); err != nil {
+		pma.SendBadRequestError(err)
+		return
+	}
 	if req.Role < 1 || req.Role > 4 {
-		pma.HandleBadRequest(fmt.Sprintf("Invalid role id %v", req.Role))
+		pma.SendBadRequestError(fmt.Errorf("Invalid role id %v", req.Role))
 		return
 	}
 	err := project.UpdateProjectMemberRole(pmID, req.Role)
 	if err != nil {
-		pma.HandleInternalServerError(fmt.Sprintf("Failed to update DB to add project user role, project id: %d, pmid : %d, role id: %d", pid, pmID, req.Role))
+		pma.SendInternalServerError(fmt.Errorf("Failed to update DB to add project user role, project id: %d, pmid : %d, role id: %d", pid, pmID, req.Role))
 		return
 	}
 }
@@ -204,7 +210,7 @@ func (pma *ProjectMemberAPI) Delete() {
 	pmid := pma.id
 	err := project.DeleteProjectMemberByID(pmid)
 	if err != nil {
-		pma.HandleInternalServerError(fmt.Sprintf("Failed to delete project roles for user, project member id: %d, error: %v", pmid, err))
+		pma.SendInternalServerError(fmt.Errorf("Failed to delete project roles for user, project member id: %d, error: %v", pmid, err))
 		return
 	}
 }
diff --git a/src/core/api/reg_gc.go b/src/core/api/reg_gc.go
index 00a381cde..d5712c6e7 100644
--- a/src/core/api/reg_gc.go
+++ b/src/core/api/reg_gc.go
@@ -15,7 +15,7 @@
 package api
 
 import (
-	"fmt"
+	"errors"
 	"net/http"
 	"os"
 	"strconv"
@@ -33,11 +33,11 @@ type GCAPI struct {
 func (gc *GCAPI) Prepare() {
 	gc.BaseController.Prepare()
 	if !gc.SecurityCtx.IsAuthenticated() {
-		gc.HandleUnauthorized()
+		gc.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 	if !gc.SecurityCtx.IsSysAdmin() {
-		gc.HandleForbidden(gc.SecurityCtx.GetUsername())
+		gc.SendForbiddenError(errors.New(gc.SecurityCtx.GetUsername()))
 		return
 	}
 }
@@ -58,7 +58,11 @@ func (gc *GCAPI) Prepare() {
 //	}
 func (gc *GCAPI) Post() {
 	ajr := models.AdminJobReq{}
-	gc.DecodeJSONReqAndValidate(&ajr)
+	isValid, err := gc.DecodeJSONReqAndValidate(&ajr)
+	if !isValid {
+		gc.SendBadRequestError(err)
+		return
+	}
 	ajr.Name = common_job.ImageGC
 	ajr.Parameters = map[string]interface{}{
 		"redis_url_reg": os.Getenv("_REDIS_URL_REG"),
@@ -77,7 +81,11 @@ func (gc *GCAPI) Post() {
 //	}
 func (gc *GCAPI) Put() {
 	ajr := models.AdminJobReq{}
-	gc.DecodeJSONReqAndValidate(&ajr)
+	isValid, err := gc.DecodeJSONReqAndValidate(&ajr)
+	if !isValid {
+		gc.SendBadRequestError(err)
+		return
+	}
 	ajr.Name = common_job.ImageGC
 	gc.updateSchedule(ajr)
 }
@@ -86,7 +94,7 @@ func (gc *GCAPI) Put() {
 func (gc *GCAPI) GetGC() {
 	id, err := gc.GetInt64FromPath(":id")
 	if err != nil {
-		gc.HandleInternalServerError(fmt.Sprintf("need to specify gc id"))
+		gc.SendInternalServerError(errors.New("need to specify gc id"))
 		return
 	}
 	gc.get(id)
@@ -106,7 +114,7 @@ func (gc *GCAPI) Get() {
 func (gc *GCAPI) GetLog() {
 	id, err := gc.GetInt64FromPath(":id")
 	if err != nil {
-		gc.HandleBadRequest("invalid ID")
+		gc.SendBadRequestError(errors.New("invalid ID"))
 		return
 	}
 	gc.getLog(id)
diff --git a/src/core/api/registry.go b/src/core/api/registry.go
index d9cc7cf1b..2348b6fc3 100644
--- a/src/core/api/registry.go
+++ b/src/core/api/registry.go
@@ -1,6 +1,7 @@
 package api
 
 import (
+	"errors"
 	"fmt"
 	"net/http"
 	"strconv"
@@ -28,12 +29,12 @@ type RegistryAPI struct {
 func (t *RegistryAPI) Prepare() {
 	t.BaseController.Prepare()
 	if !t.SecurityCtx.IsAuthenticated() {
-		t.HandleUnauthorized()
+		t.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 
 	if !t.SecurityCtx.IsSysAdmin() {
-		t.HandleForbidden(t.SecurityCtx.GetUsername())
+		t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
 		return
 	}
 
@@ -59,12 +60,12 @@ func (t *RegistryAPI) Ping() {
 	if req.ID != nil {
 		reg, err = t.manager.Get(*req.ID)
 		if err != nil {
-			t.HandleInternalServerError(fmt.Sprintf("failed to get registry %d: %v", *req.ID, err))
+			t.SendInternalServerError(fmt.Errorf("failed to get registry %d: %v", *req.ID, err))
 			return
 		}
 
 		if reg == nil {
-			t.HandleNotFound(fmt.Sprintf("registry %d not found", *req.ID))
+			t.SendNotFoundError(fmt.Errorf("registry %d not found", *req.ID))
 			return
 		}
 	}
@@ -74,7 +75,7 @@ func (t *RegistryAPI) Ping() {
 	if req.URL != nil {
 		url, err := utils.ParseEndpoint(*req.URL)
 		if err != nil {
-			t.HandleBadRequest(err.Error())
+			t.SendBadRequestError(err)
 			return
 		}
 
@@ -103,7 +104,7 @@ func (t *RegistryAPI) Ping() {
 		reg.Insecure = *req.Insecure
 	}
 	if len(reg.Type) == 0 || len(reg.URL) == 0 {
-		t.HandleBadRequest("type or url cannot be empty")
+		t.SendBadRequestError(errors.New("type or url cannot be empty"))
 		return
 	}
 
@@ -111,15 +112,15 @@ func (t *RegistryAPI) Ping() {
 	if err != nil {
 		e, ok := err.(*common_http.Error)
 		if ok && e.Code == http.StatusUnauthorized {
-			t.HandleBadRequest("invalid credential")
+			t.SendBadRequestError(errors.New("invalid credential"))
 			return
 		}
-		t.HandleInternalServerError(fmt.Sprintf("failed to check health of registry %s: %v", reg.URL, err))
+		t.SendInternalServerError(fmt.Errorf("failed to check health of registry %s: %v", reg.URL, err))
 		return
 	}
 
 	if status != model.Healthy {
-		t.HandleBadRequest("")
+		t.SendBadRequestError(errors.New(""))
 		return
 	}
 	return
@@ -127,17 +128,21 @@ func (t *RegistryAPI) Ping() {
 
 // Get gets a registry by id.
 func (t *RegistryAPI) Get() {
-	id := t.GetIDFromURL()
+	id, err := t.GetIDFromURL()
+	if err != nil {
+		t.SendBadRequestError(err)
+		return
+	}
 
 	r, err := t.manager.Get(id)
 	if err != nil {
 		log.Errorf("failed to get registry %d: %v", id, err)
-		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		t.SendInternalServerError(err)
 		return
 	}
 
 	if r == nil {
-		t.HandleNotFound(fmt.Sprintf("registry %d not found", id))
+		t.SendNotFoundError(fmt.Errorf("registry %d not found", id))
 		return
 	}
 
@@ -159,7 +164,7 @@ func (t *RegistryAPI) List() {
 	})
 	if err != nil {
 		log.Errorf("failed to list registries %s: %v", name, err)
-		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		t.SendInternalServerError(err)
 		return
 	}
 
@@ -178,34 +183,38 @@ func (t *RegistryAPI) List() {
 // Post creates a registry
 func (t *RegistryAPI) Post() {
 	r := &model.Registry{}
-	t.DecodeJSONReqAndValidate(r)
+	isValid, err := t.DecodeJSONReqAndValidate(r)
+	if !isValid {
+		t.SendBadRequestError(err)
+		return
+	}
 
 	reg, err := t.manager.GetByName(r.Name)
 	if err != nil {
 		log.Errorf("failed to get registry %s: %v", r.Name, err)
-		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		t.SendInternalServerError(err)
 		return
 	}
 
 	if reg != nil {
-		t.HandleConflict(fmt.Sprintf("name '%s' is already used", r.Name))
+		t.SendConflictError(fmt.Errorf("name '%s' is already used", r.Name))
 		return
 	}
 
 	status, err := registry.CheckHealthStatus(r)
 	if err != nil {
-		t.HandleBadRequest(fmt.Sprintf("health check to registry %s failed: %v", r.URL, err))
+		t.SendBadRequestError(fmt.Errorf("health check to registry %s failed: %v", r.URL, err))
 		return
 	}
 	if status != model.Healthy {
-		t.HandleBadRequest(fmt.Sprintf("registry %s is unhealthy: %s", r.URL, status))
+		t.SendBadRequestError(fmt.Errorf("registry %s is unhealthy: %s", r.URL, status))
 		return
 	}
 
 	id, err := t.manager.Add(r)
 	if err != nil {
 		log.Errorf("Add registry '%s' error: %v", r.URL, err)
-		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		t.SendInternalServerError(err)
 		return
 	}
 
@@ -214,22 +223,29 @@ func (t *RegistryAPI) Post() {
 
 // Put updates a registry
 func (t *RegistryAPI) Put() {
-	id := t.GetIDFromURL()
+	id, err := t.GetIDFromURL()
+	if err != nil {
+		t.SendBadRequestError(err)
+		return
+	}
 
 	r, err := t.manager.Get(id)
 	if err != nil {
 		log.Errorf("Get registry by id %d error: %v", id, err)
-		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		t.SendInternalServerError(err)
 		return
 	}
 
 	if r == nil {
-		t.HandleNotFound(fmt.Sprintf("Registry %d not found", id))
+		t.SendNotFoundError(fmt.Errorf("Registry %d not found", id))
 		return
 	}
 
 	req := models.RegistryUpdateRequest{}
-	t.DecodeJSONReq(&req)
+	if err := t.DecodeJSONReq(&req); err != nil {
+		t.SendBadRequestError(err)
+		return
+	}
 
 	originalName := r.Name
 
@@ -261,47 +277,51 @@ func (t *RegistryAPI) Put() {
 		reg, err := t.manager.GetByName(r.Name)
 		if err != nil {
 			log.Errorf("Get registry by name '%s' error: %v", r.Name, err)
-			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+			t.SendInternalServerError(err)
 			return
 		}
 
 		if reg != nil {
-			t.HandleConflict("name is already used")
+			t.SendConflictError(errors.New("name is already used"))
 			return
 		}
 	}
 
 	status, err := registry.CheckHealthStatus(r)
 	if err != nil {
-		t.HandleBadRequest(fmt.Sprintf("health check to registry %s failed: %v", r.URL, err))
+		t.SendBadRequestError(fmt.Errorf("health check to registry %s failed: %v", r.URL, err))
 		return
 	}
 	if status != model.Healthy {
-		t.HandleBadRequest(fmt.Sprintf("registry %s is unhealthy: %s", r.URL, status))
+		t.SendBadRequestError(fmt.Errorf("registry %s is unhealthy: %s", r.URL, status))
 		return
 	}
 
 	if err := t.manager.Update(r); err != nil {
 		log.Errorf("Update registry %d error: %v", id, err)
-		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		t.SendInternalServerError(err)
 		return
 	}
 }
 
 // Delete deletes a registry
 func (t *RegistryAPI) Delete() {
-	id := t.GetIDFromURL()
+	id, err := t.GetIDFromURL()
+	if err != nil {
+		t.SendBadRequestError(err)
+		return
+	}
 
 	registry, err := t.manager.Get(id)
 	if err != nil {
 		msg := fmt.Sprintf("Get registry %d error: %v", id, err)
 		log.Error(msg)
-		t.HandleInternalServerError(msg)
+		t.SendInternalServerError(errors.New(msg))
 		return
 	}
 
 	if registry == nil {
-		t.HandleNotFound(fmt.Sprintf("Registry %d not found", id))
+		t.SendNotFoundError(fmt.Errorf("Registry %d not found", id))
 		return
 	}
 
@@ -312,13 +332,13 @@ func (t *RegistryAPI) Delete() {
 		},
 	}...)
 	if err != nil {
-		t.HandleInternalServerError(fmt.Sprintf("List replication policies with source registry %d error: %v", id, err))
+		t.SendInternalServerError(fmt.Errorf("List replication policies with source registry %d error: %v", id, err))
 		return
 	}
 	if total > 0 {
 		msg := fmt.Sprintf("Can't delete registry %d,  %d replication policies use it as source registry", id, total)
 		log.Error(msg)
-		t.HandleStatusPreconditionFailed(msg)
+		t.SendPreconditionFailedError(errors.New(msg))
 		return
 	}
 
@@ -329,20 +349,20 @@ func (t *RegistryAPI) Delete() {
 		},
 	}...)
 	if err != nil {
-		t.HandleInternalServerError(fmt.Sprintf("List replication policies with destination registry %d error: %v", id, err))
+		t.SendInternalServerError(fmt.Errorf("List replication policies with destination registry %d error: %v", id, err))
 		return
 	}
 	if total > 0 {
 		msg := fmt.Sprintf("Can't delete registry %d,  %d replication policies use it as destination registry", id, total)
 		log.Error(msg)
-		t.HandleStatusPreconditionFailed(msg)
+		t.SendPreconditionFailedError(errors.New(msg))
 		return
 	}
 
 	if err := t.manager.Remove(id); err != nil {
 		msg := fmt.Sprintf("Delete registry %d error: %v", id, err)
 		log.Error(msg)
-		t.HandleInternalServerError(msg)
+		t.SendPreconditionFailedError(errors.New(msg))
 		return
 	}
 }
@@ -352,7 +372,7 @@ func (t *RegistryAPI) GetInfo() {
 	id, err := t.GetInt64FromPath(":id")
 	// "0" is used for the ID of the local Harbor registry
 	if err != nil || id < 0 {
-		t.HandleBadRequest(fmt.Sprintf("invalid registry ID %s", t.GetString(":id")))
+		t.SendBadRequestError(fmt.Errorf("invalid registry ID %s", t.GetString(":id")))
 		return
 	}
 	var registry *model.Registry
@@ -361,28 +381,28 @@ func (t *RegistryAPI) GetInfo() {
 	} else {
 		registry, err = t.manager.Get(id)
 		if err != nil {
-			t.HandleInternalServerError(fmt.Sprintf("failed to get registry %d: %v", id, err))
+			t.SendInternalServerError(fmt.Errorf("failed to get registry %d: %v", id, err))
 			return
 		}
 		if registry == nil {
-			t.HandleNotFound(fmt.Sprintf("registry %d not found", id))
+			t.SendNotFoundError(fmt.Errorf("registry %d not found", id))
 			return
 		}
 	}
 
 	factory, err := adapter.GetFactory(registry.Type)
 	if err != nil {
-		t.HandleInternalServerError(fmt.Sprintf("failed to get the adapter factory for registry type %s: %v", registry.Type, err))
+		t.SendInternalServerError(fmt.Errorf("failed to get the adapter factory for registry type %s: %v", registry.Type, err))
 		return
 	}
 	adp, err := factory(registry)
 	if err != nil {
-		t.HandleInternalServerError(fmt.Sprintf("failed to create the adapter for registry %d: %v", registry.ID, err))
+		t.SendInternalServerError(fmt.Errorf("failed to create the adapter for registry %d: %v", registry.ID, err))
 		return
 	}
 	info, err := adp.Info()
 	if err != nil {
-		t.HandleInternalServerError(fmt.Sprintf("failed to get registry info %d: %v", id, err))
+		t.SendInternalServerError(fmt.Errorf("failed to get registry info %d: %v", id, err))
 		return
 	}
 	t.WriteJSONData(process(info))
diff --git a/src/core/api/replication_adapter.go b/src/core/api/replication_adapter.go
index c2978720c..bf5df68d2 100644
--- a/src/core/api/replication_adapter.go
+++ b/src/core/api/replication_adapter.go
@@ -15,6 +15,7 @@
 package api
 
 import (
+	"errors"
 	"github.com/goharbor/harbor/src/replication/adapter"
 	"github.com/goharbor/harbor/src/replication/model"
 )
@@ -29,10 +30,10 @@ func (r *ReplicationAdapterAPI) Prepare() {
 	r.BaseController.Prepare()
 	if !r.SecurityCtx.IsSysAdmin() {
 		if !r.SecurityCtx.IsAuthenticated() {
-			r.HandleUnauthorized()
+			r.SendUnAuthorizedError(errors.New("UnAuthorized"))
 			return
 		}
-		r.HandleForbidden(r.SecurityCtx.GetUsername())
+		r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
 		return
 	}
 }
diff --git a/src/core/api/replication_execution.go b/src/core/api/replication_execution.go
index cbd4a9c91..2bba83db0 100644
--- a/src/core/api/replication_execution.go
+++ b/src/core/api/replication_execution.go
@@ -15,6 +15,7 @@
 package api
 
 import (
+	"errors"
 	"fmt"
 	"net/http"
 	"strconv"
@@ -37,10 +38,10 @@ func (r *ReplicationOperationAPI) Prepare() {
 	// we need to allow the jobservice to call the API
 	if !(r.SecurityCtx.IsSysAdmin() || r.SecurityCtx.IsSolutionUser()) {
 		if !r.SecurityCtx.IsAuthenticated() {
-			r.HandleUnauthorized()
+			r.SendUnAuthorizedError(errors.New("UnAuthorized"))
 			return
 		}
-		r.HandleForbidden(r.SecurityCtx.GetUsername())
+		r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
 		return
 	}
 }
@@ -85,15 +86,22 @@ func (r *ReplicationOperationAPI) ListExecutions() {
 	if len(r.GetString("policy_id")) > 0 {
 		policyID, err := r.GetInt64("policy_id")
 		if err != nil || policyID <= 0 {
-			r.HandleBadRequest(fmt.Sprintf("invalid policy_id %s", r.GetString("policy_id")))
+			r.SendBadRequestError(fmt.Errorf("invalid policy_id %s", r.GetString("policy_id")))
 			return
 		}
 		query.PolicyID = policyID
 	}
-	query.Page, query.Size = r.GetPaginationParams()
+	page, size, err := r.GetPaginationParams()
+	if err != nil {
+		r.SendBadRequestError(err)
+		return
+	}
+	query.Page = page
+	query.Size = size
+
 	total, executions, err := replication.OperationCtl.ListExecutions(query)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to list executions: %v", err))
+		r.SendInternalServerError(fmt.Errorf("failed to list executions: %v", err))
 		return
 	}
 	r.SetPaginationHeader(total, query.Page, query.Size)
@@ -103,29 +111,33 @@ func (r *ReplicationOperationAPI) ListExecutions() {
 // CreateExecution starts a replication
 func (r *ReplicationOperationAPI) CreateExecution() {
 	execution := &models.Execution{}
-	r.DecodeJSONReq(execution)
+	if err := r.DecodeJSONReq(execution); err != nil {
+		r.SendBadRequestError(err)
+		return
+	}
+
 	policy, err := replication.PolicyCtl.Get(execution.PolicyID)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get policy %d: %v", execution.PolicyID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get policy %d: %v", execution.PolicyID, err))
 		return
 	}
 	if policy == nil {
-		r.HandleNotFound(fmt.Sprintf("policy %d not found", execution.PolicyID))
+		r.SendNotFoundError(fmt.Errorf("policy %d not found", execution.PolicyID))
 		return
 	}
 	if !policy.Enabled {
-		r.HandleBadRequest(fmt.Sprintf("the policy %d is disabled", execution.PolicyID))
+		r.SendBadRequestError(fmt.Errorf("the policy %d is disabled", execution.PolicyID))
 		return
 	}
 	if err = event.PopulateRegistries(replication.RegistryMgr, policy); err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to populate registries for policy %d: %v", execution.PolicyID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to populate registries for policy %d: %v", execution.PolicyID, err))
 		return
 	}
 
 	trigger := r.GetString("trigger", string(model.TriggerTypeManual))
 	executionID, err := replication.OperationCtl.StartReplication(policy, nil, model.TriggerType(trigger))
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to start replication for policy %d: %v", execution.PolicyID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to start replication for policy %d: %v", execution.PolicyID, err))
 		return
 	}
 	r.Redirect(http.StatusCreated, strconv.FormatInt(executionID, 10))
@@ -135,17 +147,17 @@ func (r *ReplicationOperationAPI) CreateExecution() {
 func (r *ReplicationOperationAPI) GetExecution() {
 	executionID, err := r.GetInt64FromPath(":id")
 	if err != nil || executionID <= 0 {
-		r.HandleBadRequest("invalid execution ID")
+		r.SendBadRequestError(errors.New("invalid execution ID"))
 		return
 	}
 	execution, err := replication.OperationCtl.GetExecution(executionID)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get execution %d: %v", executionID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get execution %d: %v", executionID, err))
 		return
 	}
 
 	if execution == nil {
-		r.HandleNotFound(fmt.Sprintf("execution %d not found", executionID))
+		r.SendNotFoundError(fmt.Errorf("execution %d not found", executionID))
 		return
 	}
 	r.WriteJSONData(execution)
@@ -155,22 +167,22 @@ func (r *ReplicationOperationAPI) GetExecution() {
 func (r *ReplicationOperationAPI) StopExecution() {
 	executionID, err := r.GetInt64FromPath(":id")
 	if err != nil || executionID <= 0 {
-		r.HandleBadRequest("invalid execution ID")
+		r.SendBadRequestError(errors.New("invalid execution ID"))
 		return
 	}
 	execution, err := replication.OperationCtl.GetExecution(executionID)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get execution %d: %v", executionID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get execution %d: %v", executionID, err))
 		return
 	}
 
 	if execution == nil {
-		r.HandleNotFound(fmt.Sprintf("execution %d not found", executionID))
+		r.SendNotFoundError(fmt.Errorf("execution %d not found", executionID))
 		return
 	}
 
 	if err := replication.OperationCtl.StopReplication(executionID); err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to stop execution %d: %v", executionID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to stop execution %d: %v", executionID, err))
 		return
 	}
 }
@@ -179,17 +191,17 @@ func (r *ReplicationOperationAPI) StopExecution() {
 func (r *ReplicationOperationAPI) ListTasks() {
 	executionID, err := r.GetInt64FromPath(":id")
 	if err != nil || executionID <= 0 {
-		r.HandleBadRequest("invalid execution ID")
+		r.SendBadRequestError(errors.New("invalid execution ID"))
 		return
 	}
 
 	execution, err := replication.OperationCtl.GetExecution(executionID)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get execution %d: %v", executionID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get execution %d: %v", executionID, err))
 		return
 	}
 	if execution == nil {
-		r.HandleNotFound(fmt.Sprintf("execution %d not found", executionID))
+		r.SendNotFoundError(fmt.Errorf("execution %d not found", executionID))
 		return
 	}
 
@@ -201,10 +213,16 @@ func (r *ReplicationOperationAPI) ListTasks() {
 	if len(status) > 0 {
 		query.Statuses = []string{status}
 	}
-	query.Page, query.Size = r.GetPaginationParams()
+	page, size, err := r.GetPaginationParams()
+	if err != nil {
+		r.SendBadRequestError(err)
+		return
+	}
+	query.Page = page
+	query.Size = size
 	total, tasks, err := replication.OperationCtl.ListTasks(query)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to list tasks: %v", err))
+		r.SendInternalServerError(fmt.Errorf("failed to list tasks: %v", err))
 		return
 	}
 	r.SetPaginationHeader(total, query.Page, query.Size)
@@ -215,45 +233,45 @@ func (r *ReplicationOperationAPI) ListTasks() {
 func (r *ReplicationOperationAPI) GetTaskLog() {
 	executionID, err := r.GetInt64FromPath(":id")
 	if err != nil || executionID <= 0 {
-		r.HandleBadRequest("invalid execution ID")
+		r.SendBadRequestError(errors.New("invalid execution ID"))
 		return
 	}
 
 	execution, err := replication.OperationCtl.GetExecution(executionID)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get execution %d: %v", executionID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get execution %d: %v", executionID, err))
 		return
 	}
 	if execution == nil {
-		r.HandleNotFound(fmt.Sprintf("execution %d not found", executionID))
+		r.SendNotFoundError(fmt.Errorf("execution %d not found", executionID))
 		return
 	}
 
 	taskID, err := r.GetInt64FromPath(":tid")
 	if err != nil || taskID <= 0 {
-		r.HandleBadRequest("invalid task ID")
+		r.SendBadRequestError(errors.New("invalid task ID"))
 		return
 	}
 	task, err := replication.OperationCtl.GetTask(taskID)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get task %d: %v", taskID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get task %d: %v", taskID, err))
 		return
 	}
 	if task == nil {
-		r.HandleNotFound(fmt.Sprintf("task %d not found", taskID))
+		r.SendNotFoundError(fmt.Errorf("task %d not found", taskID))
 		return
 	}
 
 	logBytes, err := replication.OperationCtl.GetTaskLog(taskID)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get log of task %d: %v", taskID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get log of task %d: %v", taskID, err))
 		return
 	}
 	r.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Length"), strconv.Itoa(len(logBytes)))
 	r.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Type"), "text/plain")
 	_, err = r.Ctx.ResponseWriter.Write(logBytes)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to write log of task %d: %v", taskID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to write log of task %d: %v", taskID, err))
 		return
 	}
 }
diff --git a/src/core/api/replication_policy.go b/src/core/api/replication_policy.go
index da4f5b64a..b0f9617a7 100644
--- a/src/core/api/replication_policy.go
+++ b/src/core/api/replication_policy.go
@@ -15,10 +15,12 @@
 package api
 
 import (
+	"errors"
 	"fmt"
 	"net/http"
 	"strconv"
 
+	common_model "github.com/goharbor/harbor/src/common/models"
 	"github.com/goharbor/harbor/src/replication"
 	"github.com/goharbor/harbor/src/replication/dao/models"
 	"github.com/goharbor/harbor/src/replication/event"
@@ -38,30 +40,38 @@ func (r *ReplicationPolicyAPI) Prepare() {
 	r.BaseController.Prepare()
 	if !r.SecurityCtx.IsSysAdmin() {
 		if !r.SecurityCtx.IsAuthenticated() {
-			r.HandleUnauthorized()
+			r.SendUnAuthorizedError(errors.New("UnAuthorized"))
 			return
 		}
-		r.HandleForbidden(r.SecurityCtx.GetUsername())
+		r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
 		return
 	}
 }
 
 // List the replication policies
 func (r *ReplicationPolicyAPI) List() {
+	page, size, err := r.GetPaginationParams()
+	if err != nil {
+		r.SendInternalServerError(err)
+		return
+	}
 	// TODO: support more query
 	query := &model.PolicyQuery{
 		Name: r.GetString("name"),
+		Pagination: common_model.Pagination{
+			Page: page,
+			Size: size,
+		},
 	}
-	query.Page, query.Size = r.GetPaginationParams()
 
 	total, policies, err := replication.PolicyCtl.List(query)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to list policies: %v", err))
+		r.SendInternalServerError(fmt.Errorf("failed to list policies: %v", err))
 		return
 	}
 	for _, policy := range policies {
 		if err = populateRegistries(replication.RegistryMgr, policy); err != nil {
-			r.HandleInternalServerError(fmt.Sprintf("failed to populate registries for policy %d: %v", policy.ID, err))
+			r.SendInternalServerError(fmt.Errorf("failed to populate registries for policy %d: %v", policy.ID, err))
 			return
 		}
 	}
@@ -72,7 +82,11 @@ func (r *ReplicationPolicyAPI) List() {
 // Create the replication policy
 func (r *ReplicationPolicyAPI) Create() {
 	policy := &model.Policy{}
-	r.DecodeJSONReqAndValidate(policy)
+	isValid, err := r.DecodeJSONReqAndValidate(policy)
+	if !isValid {
+		r.SendBadRequestError(err)
+		return
+	}
 
 	if !r.validateName(policy) {
 		return
@@ -83,7 +97,7 @@ func (r *ReplicationPolicyAPI) Create() {
 
 	id, err := replication.PolicyCtl.Create(policy)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to create the policy: %v", err))
+		r.SendInternalServerError(fmt.Errorf("failed to create the policy: %v", err))
 		return
 	}
 	r.Redirect(http.StatusCreated, strconv.FormatInt(id, 10))
@@ -93,11 +107,11 @@ func (r *ReplicationPolicyAPI) Create() {
 func (r *ReplicationPolicyAPI) validateName(policy *model.Policy) bool {
 	p, err := replication.PolicyCtl.GetByName(policy.Name)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get policy %s: %v", policy.Name, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get policy %s: %v", policy.Name, err))
 		return false
 	}
 	if p != nil {
-		r.HandleConflict(fmt.Sprintf("policy %s already exists", policy.Name))
+		r.SendConflictError(fmt.Errorf("policy %s already exists", policy.Name))
 		return false
 	}
 	return true
@@ -113,11 +127,11 @@ func (r *ReplicationPolicyAPI) validateRegistry(policy *model.Policy) bool {
 	}
 	registry, err := replication.RegistryMgr.Get(registryID)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get registry %d: %v", registryID, err))
+		r.SendConflictError(fmt.Errorf("failed to get registry %d: %v", registryID, err))
 		return false
 	}
 	if registry == nil {
-		r.HandleNotFound(fmt.Sprintf("registry %d not found", registryID))
+		r.SendNotFoundError(fmt.Errorf("registry %d not found", registryID))
 		return false
 	}
 	return true
@@ -127,21 +141,21 @@ func (r *ReplicationPolicyAPI) validateRegistry(policy *model.Policy) bool {
 func (r *ReplicationPolicyAPI) Get() {
 	id, err := r.GetInt64FromPath(":id")
 	if id <= 0 || err != nil {
-		r.HandleBadRequest("invalid policy ID")
+		r.SendBadRequestError(errors.New("invalid policy ID"))
 		return
 	}
 
 	policy, err := replication.PolicyCtl.Get(id)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get the policy %d: %v", id, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get the policy %d: %v", id, err))
 		return
 	}
 	if policy == nil {
-		r.HandleNotFound(fmt.Sprintf("policy %d not found", id))
+		r.SendNotFoundError(fmt.Errorf("policy %d not found", id))
 		return
 	}
 	if err = populateRegistries(replication.RegistryMgr, policy); err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to populate registries for policy %d: %v", policy.ID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to populate registries for policy %d: %v", policy.ID, err))
 		return
 	}
 
@@ -152,22 +166,27 @@ func (r *ReplicationPolicyAPI) Get() {
 func (r *ReplicationPolicyAPI) Update() {
 	id, err := r.GetInt64FromPath(":id")
 	if id <= 0 || err != nil {
-		r.HandleBadRequest("invalid policy ID")
+		r.SendBadRequestError(errors.New("invalid policy ID"))
 		return
 	}
 
 	originalPolicy, err := replication.PolicyCtl.Get(id)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get the policy %d: %v", id, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get the policy %d: %v", id, err))
 		return
 	}
 	if originalPolicy == nil {
-		r.HandleNotFound(fmt.Sprintf("policy %d not found", id))
+		r.SendNotFoundError(fmt.Errorf("policy %d not found", id))
 		return
 	}
 
 	policy := &model.Policy{}
-	r.DecodeJSONReqAndValidate(policy)
+	isValid, err := r.DecodeJSONReqAndValidate(policy)
+	if !isValid {
+		r.SendBadRequestError(err)
+		return
+	}
+
 	if policy.Name != originalPolicy.Name &&
 		!r.validateName(policy) {
 		return
@@ -179,7 +198,7 @@ func (r *ReplicationPolicyAPI) Update() {
 
 	policy.ID = id
 	if err := replication.PolicyCtl.Update(policy); err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to update the policy %d: %v", id, err))
+		r.SendInternalServerError(fmt.Errorf("failed to update the policy %d: %v", id, err))
 		return
 	}
 }
@@ -188,17 +207,17 @@ func (r *ReplicationPolicyAPI) Update() {
 func (r *ReplicationPolicyAPI) Delete() {
 	id, err := r.GetInt64FromPath(":id")
 	if id <= 0 || err != nil {
-		r.HandleBadRequest("invalid policy ID")
+		r.SendBadRequestError(errors.New("invalid policy ID"))
 		return
 	}
 
 	policy, err := replication.PolicyCtl.Get(id)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get the policy %d: %v", id, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get the policy %d: %v", id, err))
 		return
 	}
 	if policy == nil {
-		r.HandleNotFound(fmt.Sprintf("policy %d not found", id))
+		r.SendNotFoundError(fmt.Errorf("policy %d not found", id))
 		return
 	}
 
@@ -206,19 +225,19 @@ func (r *ReplicationPolicyAPI) Delete() {
 		PolicyID: id,
 	})
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get the executions of policy %d: %v", id, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get the executions of policy %d: %v", id, err))
 		return
 	}
 
 	for _, execution := range executions {
 		if execution.Status == models.ExecutionStatusInProgress {
-			r.HandleStatusPreconditionFailed(fmt.Sprintf("the policy %d has running executions, can not be deleted", id))
+			r.SendInternalServerError(fmt.Errorf("the policy %d has running executions, can not be deleted", id))
 			return
 		}
 	}
 
 	if err := replication.PolicyCtl.Remove(id); err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to delete the policy %d: %v", id, err))
+		r.SendInternalServerError(fmt.Errorf("failed to delete the policy %d: %v", id, err))
 		return
 	}
 }
diff --git a/src/core/api/repository.go b/src/core/api/repository.go
index af281fda8..0d9f5f146 100644
--- a/src/core/api/repository.go
+++ b/src/core/api/repository.go
@@ -24,6 +24,7 @@ import (
 	"strings"
 	"time"
 
+	"errors"
 	"github.com/docker/distribution/manifest/schema1"
 	"github.com/docker/distribution/manifest/schema2"
 	"github.com/goharbor/harbor/src/common"
@@ -110,13 +111,13 @@ type manifestResp struct {
 func (ra *RepositoryAPI) Get() {
 	projectID, err := ra.GetInt64("project_id")
 	if err != nil || projectID <= 0 {
-		ra.HandleBadRequest(fmt.Sprintf("invalid project_id %s", ra.GetString("project_id")))
+		ra.SendBadRequestError(fmt.Errorf("invalid project_id %s", ra.GetString("project_id")))
 		return
 	}
 
 	labelID, err := ra.GetInt64("label_id", 0)
 	if err != nil {
-		ra.HandleBadRequest(fmt.Sprintf("invalid label_id: %s", ra.GetString("label_id")))
+		ra.SendBadRequestError(fmt.Errorf("invalid label_id: %s", ra.GetString("label_id")))
 		return
 	}
 
@@ -128,17 +129,17 @@ func (ra *RepositoryAPI) Get() {
 	}
 
 	if !exist {
-		ra.HandleNotFound(fmt.Sprintf("project %d not found", projectID))
+		ra.SendNotFoundError(fmt.Errorf("project %d not found", projectID))
 		return
 	}
 
 	resource := rbac.NewProjectNamespace(projectID).Resource(rbac.ResourceRepository)
 	if !ra.SecurityCtx.Can(rbac.ActionList, resource) {
 		if !ra.SecurityCtx.IsAuthenticated() {
-			ra.HandleUnauthorized()
+			ra.SendUnAuthorizedError(errors.New("Unauthorized"))
 			return
 		}
-		ra.HandleForbidden(ra.SecurityCtx.GetUsername())
+		ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
 		return
 	}
 
@@ -147,19 +148,24 @@ func (ra *RepositoryAPI) Get() {
 		Name:       ra.GetString("q"),
 		LabelID:    labelID,
 	}
-	query.Page, query.Size = ra.GetPaginationParams()
+	query.Page, query.Size, err = ra.GetPaginationParams()
+	if err != nil {
+		ra.SendBadRequestError(err)
+		return
+	}
+
 	query.Sort = ra.GetString("sort")
 
 	total, err := dao.GetTotalOfRepositories(query)
 	if err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("failed to get total of repositories of project %d: %v",
+		ra.SendInternalServerError(fmt.Errorf("failed to get total of repositories of project %d: %v",
 			projectID, err))
 		return
 	}
 
 	repositories, err := getRepositories(query)
 	if err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("failed to get repository: %v", err))
+		ra.SendInternalServerError(fmt.Errorf("failed to get repository: %v", err))
 		return
 	}
 
@@ -240,25 +246,26 @@ func (ra *RepositoryAPI) Delete() {
 	}
 
 	if project == nil {
-		ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName))
+		ra.SendNotFoundError(fmt.Errorf("project %s not found", projectName))
 		return
 	}
 
 	if !ra.SecurityCtx.IsAuthenticated() {
-		ra.HandleUnauthorized()
+		ra.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 
 	resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceRepository)
 	if !ra.SecurityCtx.Can(rbac.ActionDelete, resource) {
-		ra.HandleForbidden(ra.SecurityCtx.GetUsername())
+		ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
 		return
 	}
 
 	rc, err := coreutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repoName)
 	if err != nil {
 		log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err)
-		ra.CustomAbort(http.StatusInternalServerError, "internal error")
+		ra.SendInternalServerError(errors.New("internal error"))
+		return
 	}
 
 	tags := []string{}
@@ -266,18 +273,13 @@ func (ra *RepositoryAPI) Delete() {
 	if len(tag) == 0 {
 		tagList, err := rc.ListTag()
 		if err != nil {
-			log.Errorf("error occurred while listing tags of %s: %v", repoName, err)
-
-			if regErr, ok := err.(*commonhttp.Error); ok {
-				ra.CustomAbort(regErr.Code, regErr.Message)
-			}
-
-			ra.CustomAbort(http.StatusInternalServerError, "internal error")
+			ra.ParseAndHandleError(fmt.Sprintf("error occurred while listing tags of %s", repoName), err)
+			return
 		}
 
 		// TODO remove the logic if the bug of registry is fixed
 		if len(tagList) == 0 {
-			ra.HandleNotFound(fmt.Sprintf("no tags found for repository %s", repoName))
+			ra.SendNotFoundError(fmt.Errorf("no tags found for repository %s", repoName))
 			return
 		}
 
@@ -289,7 +291,7 @@ func (ra *RepositoryAPI) Delete() {
 	if config.WithNotary() {
 		signedTags, err := getSignatures(ra.SecurityCtx.GetUsername(), repoName)
 		if err != nil {
-			ra.HandleInternalServerError(fmt.Sprintf(
+			ra.SendInternalServerError(fmt.Errorf(
 				"failed to get signatures for repository %s: %v", repoName, err))
 			return
 		}
@@ -298,12 +300,14 @@ func (ra *RepositoryAPI) Delete() {
 			digest, _, err := rc.ManifestExist(t)
 			if err != nil {
 				log.Errorf("Failed to Check the digest of tag: %s, error: %v", t, err.Error())
-				ra.CustomAbort(http.StatusInternalServerError, err.Error())
+				ra.SendInternalServerError(err)
+				return
 			}
 			log.Debugf("Tag: %s, digest: %s", t, digest)
 			if _, ok := signedTags[digest]; ok {
 				log.Errorf("Found signed tag, repostory: %s, tag: %s, deletion will be canceled", repoName, t)
-				ra.CustomAbort(http.StatusPreconditionFailed, fmt.Sprintf("tag %s is signed", t))
+				ra.SendPreconditionFailedError(fmt.Errorf("tag %s is signed", t))
+				return
 			}
 		}
 	}
@@ -311,7 +315,7 @@ func (ra *RepositoryAPI) Delete() {
 	for _, t := range tags {
 		image := fmt.Sprintf("%s:%s", repoName, t)
 		if err = dao.DeleteLabelsOfResource(common.ResourceTypeImage, image); err != nil {
-			ra.HandleInternalServerError(fmt.Sprintf("failed to delete labels of image %s: %v", image, err))
+			ra.SendInternalServerError(fmt.Errorf("failed to delete labels of image %s: %v", image, err))
 			return
 		}
 		if err = rc.DeleteTag(t); err != nil {
@@ -319,11 +323,9 @@ func (ra *RepositoryAPI) Delete() {
 				if regErr.Code == http.StatusNotFound {
 					continue
 				}
-				log.Errorf("failed to delete tag %s: %v", t, err)
-				ra.CustomAbort(regErr.Code, regErr.Message)
 			}
-			log.Errorf("error occurred while deleting tag %s:%s: %v", repoName, t, err)
-			ra.CustomAbort(http.StatusInternalServerError, "internal error")
+			ra.ParseAndHandleError(fmt.Sprintf("failed to delete tag %s", t), err)
+			return
 		}
 		log.Infof("delete tag: %s:%s", repoName, t)
 
@@ -363,12 +365,13 @@ func (ra *RepositoryAPI) Delete() {
 	exist, err := repositoryExist(repoName, rc)
 	if err != nil {
 		log.Errorf("failed to check the existence of repository %s: %v", repoName, err)
-		ra.CustomAbort(http.StatusInternalServerError, "")
+		ra.SendInternalServerError(fmt.Errorf("failed to check the existence of repository %s: %v", repoName, err))
+		return
 	}
 	if !exist {
 		repository, err := dao.GetRepositoryByName(repoName)
 		if err != nil {
-			ra.HandleInternalServerError(fmt.Sprintf("failed to get repository %s: %v", repoName, err))
+			ra.SendInternalServerError(fmt.Errorf("failed to get repository %s: %v", repoName, err))
 			return
 		}
 		if repository == nil {
@@ -378,13 +381,14 @@ func (ra *RepositoryAPI) Delete() {
 
 		if err = dao.DeleteLabelsOfResource(common.ResourceTypeRepository,
 			strconv.FormatInt(repository.RepositoryID, 10)); err != nil {
-			ra.HandleInternalServerError(fmt.Sprintf("failed to delete labels of repository %s: %v",
+			ra.SendInternalServerError(fmt.Errorf("failed to delete labels of repository %s: %v",
 				repoName, err))
 			return
 		}
 		if err = dao.DeleteRepository(repoName); err != nil {
 			log.Errorf("failed to delete repository %s: %v", repoName, err)
-			ra.CustomAbort(http.StatusInternalServerError, "")
+			ra.SendInternalServerError(fmt.Errorf("failed to delete repository %s: %v", repoName, err))
+			return
 		}
 	}
 }
@@ -395,38 +399,38 @@ func (ra *RepositoryAPI) GetTag() {
 	tag := ra.GetString(":tag")
 	exist, _, err := ra.checkExistence(repository, tag)
 	if err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("failed to check the existence of resource, error: %v", err))
+		ra.SendInternalServerError(fmt.Errorf("failed to check the existence of resource, error: %v", err))
 		return
 	}
 	if !exist {
-		ra.HandleNotFound(fmt.Sprintf("resource: %s:%s not found", repository, tag))
+		ra.SendNotFoundError(fmt.Errorf("resource: %s:%s not found", repository, tag))
 		return
 	}
 	project, _ := utils.ParseRepository(repository)
 	resource := rbac.NewProjectNamespace(project).Resource(rbac.ResourceRepositoryTag)
 	if !ra.SecurityCtx.Can(rbac.ActionRead, resource) {
 		if !ra.SecurityCtx.IsAuthenticated() {
-			ra.HandleUnauthorized()
+			ra.SendUnAuthorizedError(errors.New("UnAuthorized"))
 			return
 		}
-		ra.HandleForbidden(ra.SecurityCtx.GetUsername())
+		ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
 		return
 	}
 
 	client, err := coreutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repository)
 	if err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("failed to initialize the client for %s: %v",
+		ra.SendInternalServerError(fmt.Errorf("failed to initialize the client for %s: %v",
 			repository, err))
 		return
 	}
 
 	_, exist, err = client.ManifestExist(tag)
 	if err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("failed to check the existence of %s:%s: %v", repository, tag, err))
+		ra.SendInternalServerError(fmt.Errorf("failed to check the existence of %s:%s: %v", repository, tag, err))
 		return
 	}
 	if !exist {
-		ra.HandleNotFound(fmt.Sprintf("%s not found", tag))
+		ra.SendNotFoundError(fmt.Errorf("%s not found", tag))
 		return
 	}
 
@@ -439,38 +443,41 @@ func (ra *RepositoryAPI) GetTag() {
 // Retag tags an existing image to another tag in this repo, the source image is specified by request body.
 func (ra *RepositoryAPI) Retag() {
 	if !ra.SecurityCtx.IsAuthenticated() {
-		ra.HandleUnauthorized()
+		ra.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 
 	repoName := ra.GetString(":splat")
 	project, repo := utils.ParseRepository(repoName)
 	if !utils.ValidateRepo(repo) {
-		ra.HandleBadRequest(fmt.Sprintf("invalid repo '%s'", repo))
+		ra.SendBadRequestError(fmt.Errorf("invalid repo '%s'", repo))
 		return
 	}
 
 	request := models.RetagRequest{}
-	ra.DecodeJSONReq(&request)
+	if err := ra.DecodeJSONReq(&request); err != nil {
+		ra.SendBadRequestError(err)
+		return
+	}
 	srcImage, err := models.ParseImage(request.SrcImage)
 	if err != nil {
-		ra.HandleBadRequest(fmt.Sprintf("invalid src image string '%s', should in format '<project>/<repo>:<tag>'", request.SrcImage))
+		ra.SendBadRequestError(fmt.Errorf("invalid src image string '%s', should in format '<project>/<repo>:<tag>'", request.SrcImage))
 		return
 	}
 
 	if !utils.ValidateTag(request.Tag) {
-		ra.HandleBadRequest(fmt.Sprintf("invalid tag '%s'", request.Tag))
+		ra.SendBadRequestError(fmt.Errorf("invalid tag '%s'", request.Tag))
 		return
 	}
 
 	// Check whether source image exists
 	exist, _, err := ra.checkExistence(fmt.Sprintf("%s/%s", srcImage.Project, srcImage.Repo), srcImage.Tag)
 	if err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("check existence of %s error: %v", request.SrcImage, err))
+		ra.SendInternalServerError(fmt.Errorf("check existence of %s error: %v", request.SrcImage, err))
 		return
 	}
 	if !exist {
-		ra.HandleNotFound(fmt.Sprintf("image %s not exist", request.SrcImage))
+		ra.SendNotFoundError(fmt.Errorf("image %s not exist", request.SrcImage))
 		return
 	}
 
@@ -481,7 +488,7 @@ func (ra *RepositoryAPI) Retag() {
 		return
 	}
 	if !exist {
-		ra.HandleNotFound(fmt.Sprintf("project %s not found", project))
+		ra.SendNotFoundError(fmt.Errorf("project %s not found", project))
 		return
 	}
 
@@ -489,11 +496,11 @@ func (ra *RepositoryAPI) Retag() {
 	if !request.Override {
 		exist, _, err := ra.checkExistence(repoName, request.Tag)
 		if err != nil {
-			ra.HandleInternalServerError(fmt.Sprintf("check existence of %s:%s error: %v", repoName, request.Tag, err))
+			ra.SendInternalServerError(fmt.Errorf("check existence of %s:%s error: %v", repoName, request.Tag, err))
 			return
 		}
 		if exist {
-			ra.HandleConflict(fmt.Sprintf("tag '%s' already existed for '%s'", request.Tag, repoName))
+			ra.SendConflictError(fmt.Errorf("tag '%s' already existed for '%s'", request.Tag, repoName))
 			return
 		}
 	}
@@ -502,7 +509,7 @@ func (ra *RepositoryAPI) Retag() {
 	srcResource := rbac.NewProjectNamespace(srcImage.Project).Resource(rbac.ResourceRepository)
 	if !ra.SecurityCtx.Can(rbac.ActionPull, srcResource) {
 		log.Errorf("user has no read permission to project '%s'", srcImage.Project)
-		ra.HandleForbidden(fmt.Sprintf("%s has no read permission to project %s", ra.SecurityCtx.GetUsername(), srcImage.Project))
+		ra.SendForbiddenError(fmt.Errorf("%s has no read permission to project %s", ra.SecurityCtx.GetUsername(), srcImage.Project))
 		return
 	}
 
@@ -510,7 +517,7 @@ func (ra *RepositoryAPI) Retag() {
 	destResource := rbac.NewProjectNamespace(project).Resource(rbac.ResourceRepository)
 	if !ra.SecurityCtx.Can(rbac.ActionPush, destResource) {
 		log.Errorf("user has no write permission to project '%s'", project)
-		ra.HandleForbidden(fmt.Sprintf("%s has no write permission to project %s", ra.SecurityCtx.GetUsername(), project))
+		ra.SendForbiddenError(fmt.Errorf("%s has no write permission to project %s", ra.SecurityCtx.GetUsername(), project))
 		return
 	}
 
@@ -520,7 +527,7 @@ func (ra *RepositoryAPI) Retag() {
 		Repo:    repo,
 		Tag:     request.Tag,
 	}); err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("%v", err))
+		ra.SendInternalServerError(fmt.Errorf("%v", err))
 	}
 }
 
@@ -529,7 +536,7 @@ func (ra *RepositoryAPI) GetTags() {
 	repoName := ra.GetString(":splat")
 	labelID, err := ra.GetInt64("label_id", 0)
 	if err != nil {
-		ra.HandleBadRequest(fmt.Sprintf("invalid label_id: %s", ra.GetString("label_id")))
+		ra.SendBadRequestError(fmt.Errorf("invalid label_id: %s", ra.GetString("label_id")))
 		return
 	}
 
@@ -542,29 +549,30 @@ func (ra *RepositoryAPI) GetTags() {
 	}
 
 	if !exist {
-		ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName))
+		ra.SendNotFoundError(fmt.Errorf("project %s not found", projectName))
 		return
 	}
 
 	resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepositoryTag)
 	if !ra.SecurityCtx.Can(rbac.ActionList, resource) {
 		if !ra.SecurityCtx.IsAuthenticated() {
-			ra.HandleUnauthorized()
+			ra.SendUnAuthorizedError(errors.New("UnAuthorized"))
 			return
 		}
-		ra.HandleForbidden(ra.SecurityCtx.GetUsername())
+		ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
 		return
 	}
 
 	client, err := coreutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repoName)
 	if err != nil {
 		log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err)
-		ra.CustomAbort(http.StatusInternalServerError, "internal error")
+		ra.SendInternalServerError(errors.New("internal error"))
+		return
 	}
 
 	tags, err := client.ListTag()
 	if err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("failed to get tag of %s: %v", repoName, err))
+		ra.SendInternalServerError(fmt.Errorf("failed to get tag of %s: %v", repoName, err))
 		return
 	}
 
@@ -575,7 +583,7 @@ func (ra *RepositoryAPI) GetTags() {
 			ResourceType: common.ResourceTypeImage,
 		})
 		if err != nil {
-			ra.HandleInternalServerError(fmt.Sprintf("failed to list resource labels: %v", err))
+			ra.SendInternalServerError(fmt.Errorf("failed to list resource labels: %v", err))
 			return
 		}
 		labeledTags := map[string]struct{}{}
@@ -738,7 +746,7 @@ func (ra *RepositoryAPI) GetManifests() {
 	}
 
 	if version != "v1" && version != "v2" {
-		ra.HandleBadRequest("version should be v1 or v2")
+		ra.SendBadRequestError(errors.New("version should be v1 or v2"))
 		return
 	}
 
@@ -751,36 +759,32 @@ func (ra *RepositoryAPI) GetManifests() {
 	}
 
 	if !exist {
-		ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName))
+		ra.SendNotFoundError(fmt.Errorf("project %s not found", projectName))
 		return
 	}
 
 	resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepositoryTagManifest)
 	if !ra.SecurityCtx.Can(rbac.ActionRead, resource) {
 		if !ra.SecurityCtx.IsAuthenticated() {
-			ra.HandleUnauthorized()
+			ra.SendUnAuthorizedError(errors.New("Unauthorized"))
 			return
 		}
 
-		ra.HandleForbidden(ra.SecurityCtx.GetUsername())
+		ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
 		return
 	}
 
 	rc, err := coreutils.NewRepositoryClientForUI(ra.SecurityCtx.GetUsername(), repoName)
 	if err != nil {
 		log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err)
-		ra.CustomAbort(http.StatusInternalServerError, "internal error")
+		ra.SendInternalServerError(errors.New("internal error"))
+		return
 	}
 
 	manifest, err := getManifest(rc, tag, version)
 	if err != nil {
-		log.Errorf("error occurred while getting manifest of %s:%s: %v", repoName, tag, err)
-
-		if regErr, ok := err.(*commonhttp.Error); ok {
-			ra.CustomAbort(regErr.Code, regErr.Message)
-		}
-
-		ra.CustomAbort(http.StatusInternalServerError, "internal error")
+		ra.ParseAndHandleError(fmt.Sprintf("error occurred while getting manifest of %s:%s", repoName, tag), err)
+		return
 	}
 
 	ra.Data["json"] = manifest
@@ -833,7 +837,7 @@ func getManifest(client *registry.Repository,
 func (ra *RepositoryAPI) GetTopRepos() {
 	count, err := ra.GetInt("count", 10)
 	if err != nil || count <= 0 {
-		ra.HandleBadRequest(fmt.Sprintf("invalid count: %s", ra.GetString("count")))
+		ra.SendBadRequestError(fmt.Errorf("invalid count: %s", ra.GetString("count")))
 		return
 	}
 
@@ -846,7 +850,7 @@ func (ra *RepositoryAPI) GetTopRepos() {
 	if ra.SecurityCtx.IsAuthenticated() {
 		list, err := ra.SecurityCtx.GetMyProjects()
 		if err != nil {
-			ra.HandleInternalServerError(fmt.Sprintf("failed to get projects which the user %s is a member of: %v",
+			ra.SendInternalServerError(fmt.Errorf("failed to get projects which the user %s is a member of: %v",
 				ra.SecurityCtx.GetUsername(), err))
 			return
 		}
@@ -860,7 +864,8 @@ func (ra *RepositoryAPI) GetTopRepos() {
 	repos, err := dao.GetTopRepos(projectIDs, count)
 	if err != nil {
 		log.Errorf("failed to get top repos: %v", err)
-		ra.CustomAbort(http.StatusInternalServerError, "internal server error")
+		ra.SendInternalServerError(errors.New("internal server error"))
+		return
 	}
 
 	ra.Data["json"] = assembleReposInParallel(repos)
@@ -872,35 +877,38 @@ func (ra *RepositoryAPI) Put() {
 	name := ra.GetString(":splat")
 	repository, err := dao.GetRepositoryByName(name)
 	if err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("failed to get repository %s: %v", name, err))
+		ra.SendInternalServerError(fmt.Errorf("failed to get repository %s: %v", name, err))
 		return
 	}
 
 	if repository == nil {
-		ra.HandleNotFound(fmt.Sprintf("repository %s not found", name))
+		ra.SendNotFoundError(fmt.Errorf("repository %s not found", name))
 		return
 	}
 
 	if !ra.SecurityCtx.IsAuthenticated() {
-		ra.HandleUnauthorized()
+		ra.SendUnAuthorizedError(errors.New("Unauthorized"))
 		return
 	}
 
 	project, _ := utils.ParseRepository(name)
 	resource := rbac.NewProjectNamespace(project).Resource(rbac.ResourceRepository)
 	if !ra.SecurityCtx.Can(rbac.ActionUpdate, resource) {
-		ra.HandleForbidden(ra.SecurityCtx.GetUsername())
+		ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
 		return
 	}
 
 	desc := struct {
 		Description string `json:"description"`
 	}{}
-	ra.DecodeJSONReq(&desc)
+	if err := ra.DecodeJSONReq(&desc); err != nil {
+		ra.SendBadRequestError(err)
+		return
+	}
 
 	repository.Description = desc.Description
 	if err = dao.UpdateRepository(*repository); err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("failed to update repository %s: %v", name, err))
+		ra.SendInternalServerError(fmt.Errorf("failed to update repository %s: %v", name, err))
 		return
 	}
 }
@@ -918,17 +926,17 @@ func (ra *RepositoryAPI) GetSignatures() {
 	}
 
 	if !exist {
-		ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName))
+		ra.SendNotFoundError(fmt.Errorf("project %s not found", projectName))
 		return
 	}
 
 	resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepository)
 	if !ra.SecurityCtx.Can(rbac.ActionRead, resource) {
 		if !ra.SecurityCtx.IsAuthenticated() {
-			ra.HandleUnauthorized()
+			ra.SendUnAuthorizedError(errors.New("Unauthorized"))
 			return
 		}
-		ra.HandleForbidden(ra.SecurityCtx.GetUsername())
+		ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
 		return
 	}
 
@@ -936,7 +944,8 @@ func (ra *RepositoryAPI) GetSignatures() {
 		ra.SecurityCtx.GetUsername(), repoName)
 	if err != nil {
 		log.Errorf("Error while fetching signature from notary: %v", err)
-		ra.CustomAbort(http.StatusInternalServerError, "internal error")
+		ra.SendInternalServerError(errors.New("internal error"))
+		return
 	}
 	ra.Data["json"] = targets
 	ra.ServeJSON()
@@ -946,7 +955,7 @@ func (ra *RepositoryAPI) GetSignatures() {
 func (ra *RepositoryAPI) ScanImage() {
 	if !config.WithClair() {
 		log.Warningf("Harbor is not deployed with Clair, scan is disabled.")
-		ra.RenderError(http.StatusServiceUnavailable, "")
+		ra.SendInternalServerError(errors.New("harbor is not deployed with Clair, scan is disabled"))
 		return
 	}
 	repoName := ra.GetString(":splat")
@@ -959,23 +968,23 @@ func (ra *RepositoryAPI) ScanImage() {
 		return
 	}
 	if !exist {
-		ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName))
+		ra.SendNotFoundError(fmt.Errorf("project %s not found", projectName))
 		return
 	}
 	if !ra.SecurityCtx.IsAuthenticated() {
-		ra.HandleUnauthorized()
+		ra.SendUnAuthorizedError(errors.New("Unauthorized"))
 		return
 	}
 
 	resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepositoryTagScanJob)
 	if !ra.SecurityCtx.Can(rbac.ActionCreate, resource) {
-		ra.HandleForbidden(ra.SecurityCtx.GetUsername())
+		ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
 		return
 	}
 	err = coreutils.TriggerImageScan(repoName, tag)
 	if err != nil {
 		log.Errorf("Error while calling job service to trigger image scan: %v", err)
-		ra.HandleInternalServerError("Failed to scan image, please check log for details")
+		ra.SendInternalServerError(errors.New("Failed to scan image, please check log for details"))
 		return
 	}
 }
@@ -984,18 +993,18 @@ func (ra *RepositoryAPI) ScanImage() {
 func (ra *RepositoryAPI) VulnerabilityDetails() {
 	if !config.WithClair() {
 		log.Warningf("Harbor is not deployed with Clair, it's not impossible to get vulnerability details.")
-		ra.RenderError(http.StatusServiceUnavailable, "")
+		ra.SendInternalServerError(errors.New("harbor is not deployed with Clair, it's not impossible to get vulnerability details"))
 		return
 	}
 	repository := ra.GetString(":splat")
 	tag := ra.GetString(":tag")
 	exist, digest, err := ra.checkExistence(repository, tag)
 	if err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("failed to check the existence of resource, error: %v", err))
+		ra.SendInternalServerError(fmt.Errorf("failed to check the existence of resource, error: %v", err))
 		return
 	}
 	if !exist {
-		ra.HandleNotFound(fmt.Sprintf("resource: %s:%s not found", repository, tag))
+		ra.SendNotFoundError(fmt.Errorf("resource: %s:%s not found", repository, tag))
 		return
 	}
 	project, _ := utils.ParseRepository(repository)
@@ -1003,16 +1012,16 @@ func (ra *RepositoryAPI) VulnerabilityDetails() {
 	resource := rbac.NewProjectNamespace(project).Resource(rbac.ResourceRepositoryTagVulnerability)
 	if !ra.SecurityCtx.Can(rbac.ActionList, resource) {
 		if !ra.SecurityCtx.IsAuthenticated() {
-			ra.HandleUnauthorized()
+			ra.SendUnAuthorizedError(errors.New("Unauthorized"))
 			return
 		}
-		ra.HandleForbidden(ra.SecurityCtx.GetUsername())
+		ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
 		return
 	}
 	res := []*models.VulnerabilityItem{}
 	overview, err := dao.GetImgScanOverview(digest)
 	if err != nil {
-		ra.HandleInternalServerError(fmt.Sprintf("failed to get the scan overview, error: %v", err))
+		ra.SendInternalServerError(fmt.Errorf("failed to get the scan overview, error: %v", err))
 		return
 	}
 	if overview != nil && len(overview.DetailsKey) > 0 {
@@ -1020,7 +1029,7 @@ func (ra *RepositoryAPI) VulnerabilityDetails() {
 		log.Debugf("The key for getting details: %s", overview.DetailsKey)
 		details, err := clairClient.GetResult(overview.DetailsKey)
 		if err != nil {
-			ra.HandleInternalServerError(fmt.Sprintf("Failed to get scan details from Clair, error: %v", err))
+			ra.SendInternalServerError(fmt.Errorf("Failed to get scan details from Clair, error: %v", err))
 			return
 		}
 		res = transformVulnerabilities(details)
diff --git a/src/core/api/repository_label.go b/src/core/api/repository_label.go
index 5b658e424..53c606507 100644
--- a/src/core/api/repository_label.go
+++ b/src/core/api/repository_label.go
@@ -114,7 +114,10 @@ func (r *RepositoryLabelAPI) isValidLabelReq() bool {
 	}
 
 	l := &models.Label{}
-	r.DecodeJSONReq(l)
+	if err := r.DecodeJSONReq(l); err != nil {
+		r.SendBadRequestError(err)
+		return false
+	}
 
 	label, ok := r.validate(l.ID, p.ProjectID)
 	if !ok {
diff --git a/src/core/api/robot.go b/src/core/api/robot.go
index e500bc243..96fb7339f 100644
--- a/src/core/api/robot.go
+++ b/src/core/api/robot.go
@@ -15,6 +15,7 @@
 package api
 
 import (
+	"errors"
 	"fmt"
 	"github.com/goharbor/harbor/src/common"
 	"github.com/goharbor/harbor/src/common/dao"
@@ -41,7 +42,7 @@ func (r *RobotAPI) Prepare() {
 	method := r.Ctx.Request.Method
 
 	if !r.SecurityCtx.IsAuthenticated() {
-		r.HandleUnauthorized()
+		r.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 
@@ -53,7 +54,7 @@ func (r *RobotAPI) Prepare() {
 		} else {
 			errMsg = "invalid project ID: " + fmt.Sprintf("%d", pid)
 		}
-		r.HandleBadRequest(errMsg)
+		r.SendBadRequestError(errors.New(errMsg))
 		return
 	}
 	project, err := r.ProjectMgr.Get(pid)
@@ -62,7 +63,7 @@ func (r *RobotAPI) Prepare() {
 		return
 	}
 	if project == nil {
-		r.HandleNotFound(fmt.Sprintf("project %d not found", pid))
+		r.SendNotFoundError(fmt.Errorf("project %d not found", pid))
 		return
 	}
 	r.project = project
@@ -70,18 +71,18 @@ func (r *RobotAPI) Prepare() {
 	if method == http.MethodPut || method == http.MethodDelete {
 		id, err := r.GetInt64FromPath(":id")
 		if err != nil || id <= 0 {
-			r.HandleBadRequest("invalid robot ID")
+			r.SendBadRequestError(errors.New("invalid robot ID"))
 			return
 		}
 
 		robot, err := dao.GetRobotByID(id)
 		if err != nil {
-			r.HandleInternalServerError(fmt.Sprintf("failed to get robot %d: %v", id, err))
+			r.SendInternalServerError(fmt.Errorf("failed to get robot %d: %v", id, err))
 			return
 		}
 
 		if robot == nil {
-			r.HandleNotFound(fmt.Sprintf("robot %d not found", id))
+			r.SendNotFoundError(fmt.Errorf("robot %d not found", id))
 			return
 		}
 
@@ -92,7 +93,7 @@ func (r *RobotAPI) Prepare() {
 func (r *RobotAPI) requireAccess(action rbac.Action) bool {
 	resource := rbac.NewProjectNamespace(r.project.ProjectID).Resource(rbac.ResourceRobot)
 	if !r.SecurityCtx.Can(action, resource) {
-		r.HandleForbidden(r.SecurityCtx.GetUsername())
+		r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
 		return false
 	}
 
@@ -106,10 +107,14 @@ func (r *RobotAPI) Post() {
 	}
 
 	var robotReq models.RobotReq
+	if err := r.DecodeJSONReq(&robotReq); err != nil {
+		r.SendBadRequestError(err)
+		return
+	}
+
 	// Token duration in minutes
 	tokenDuration := time.Duration(config.RobotTokenDuration()) * time.Minute
 	expiresAt := time.Now().UTC().Add(tokenDuration).Unix()
-	r.DecodeJSONReq(&robotReq)
 	createdName := common.RobotPrefix + robotReq.Name
 
 	// first to add a robot account, and get its id.
@@ -122,10 +127,10 @@ func (r *RobotAPI) Post() {
 	id, err := dao.AddRobot(&robot)
 	if err != nil {
 		if err == dao.ErrDupRows {
-			r.HandleConflict()
+			r.SendConflictError(errors.New("conflict robot account"))
 			return
 		}
-		r.HandleInternalServerError(fmt.Sprintf("failed to create robot account: %v", err))
+		r.SendInternalServerError(fmt.Errorf("failed to create robot account: %v", err))
 		return
 	}
 
@@ -133,20 +138,20 @@ func (r *RobotAPI) Post() {
 	// token is not stored in the database.
 	jwtToken, err := token.New(id, r.project.ProjectID, expiresAt, robotReq.Access)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to valid parameters to generate token for robot account, %v", err))
+		r.SendInternalServerError(fmt.Errorf("failed to valid parameters to generate token for robot account, %v", err))
 		err := dao.DeleteRobot(id)
 		if err != nil {
-			r.HandleInternalServerError(fmt.Sprintf("failed to delete the robot account: %d, %v", id, err))
+			r.SendInternalServerError(fmt.Errorf("failed to delete the robot account: %d, %v", id, err))
 		}
 		return
 	}
 
 	rawTk, err := jwtToken.Raw()
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to sign token for robot account, %v", err))
+		r.SendInternalServerError(fmt.Errorf("failed to sign token for robot account, %v", err))
 		err := dao.DeleteRobot(id)
 		if err != nil {
-			r.HandleInternalServerError(fmt.Sprintf("failed to delete the robot account: %d, %v", id, err))
+			r.SendInternalServerError(fmt.Errorf("failed to delete the robot account: %d, %v", id, err))
 		}
 		return
 	}
@@ -172,14 +177,18 @@ func (r *RobotAPI) List() {
 
 	count, err := dao.CountRobot(&query)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to list robots on project: %d, %v", r.project.ProjectID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to list robots on project: %d, %v", r.project.ProjectID, err))
+		return
+	}
+	query.Page, query.Size, err = r.GetPaginationParams()
+	if err != nil {
+		r.SendBadRequestError(err)
 		return
 	}
-	query.Page, query.Size = r.GetPaginationParams()
 
 	robots, err := dao.ListRobots(&query)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get robots %v", err))
+		r.SendInternalServerError(fmt.Errorf("failed to get robots %v", err))
 		return
 	}
 
@@ -196,17 +205,17 @@ func (r *RobotAPI) Get() {
 
 	id, err := r.GetInt64FromPath(":id")
 	if err != nil || id <= 0 {
-		r.HandleBadRequest(fmt.Sprintf("invalid robot ID: %s", r.GetStringFromPath(":id")))
+		r.SendBadRequestError(fmt.Errorf("invalid robot ID: %s", r.GetStringFromPath(":id")))
 		return
 	}
 
 	robot, err := dao.GetRobotByID(id)
 	if err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to get robot %d: %v", id, err))
+		r.SendInternalServerError(fmt.Errorf("failed to get robot %d: %v", id, err))
 		return
 	}
 	if robot == nil {
-		r.HandleNotFound(fmt.Sprintf("robot %d not found", id))
+		r.SendNotFoundError(fmt.Errorf("robot %d not found", id))
 		return
 	}
 
@@ -221,11 +230,16 @@ func (r *RobotAPI) Put() {
 	}
 
 	var robotReq models.RobotReq
-	r.DecodeJSONReqAndValidate(&robotReq)
+	isValid, err := r.DecodeJSONReqAndValidate(&robotReq)
+	if !isValid {
+		r.SendBadRequestError(err)
+		return
+	}
+
 	r.robot.Disabled = robotReq.Disabled
 
 	if err := dao.UpdateRobot(r.robot); err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to update robot %d: %v", r.robot.ID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to update robot %d: %v", r.robot.ID, err))
 		return
 	}
 
@@ -238,7 +252,7 @@ func (r *RobotAPI) Delete() {
 	}
 
 	if err := dao.DeleteRobot(r.robot.ID); err != nil {
-		r.HandleInternalServerError(fmt.Sprintf("failed to delete robot %d: %v", r.robot.ID, err))
+		r.SendInternalServerError(fmt.Errorf("failed to delete robot %d: %v", r.robot.ID, err))
 		return
 	}
 }
diff --git a/src/core/api/scan_all.go b/src/core/api/scan_all.go
index 609db2371..11ca8ab44 100644
--- a/src/core/api/scan_all.go
+++ b/src/core/api/scan_all.go
@@ -1,6 +1,7 @@
 package api
 
 import (
+	"errors"
 	"net/http"
 	"strconv"
 
@@ -20,15 +21,15 @@ func (sc *ScanAllAPI) Prepare() {
 	sc.BaseController.Prepare()
 	if !config.WithClair() {
 		log.Warningf("Harbor is not deployed with Clair, it's not possible to scan images.")
-		sc.RenderError(http.StatusServiceUnavailable, "")
+		sc.SendStatusServiceUnavailableError(errors.New(""))
 		return
 	}
 	if !sc.SecurityCtx.IsAuthenticated() {
-		sc.HandleUnauthorized()
+		sc.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 	if !sc.SecurityCtx.IsSysAdmin() {
-		sc.HandleForbidden(sc.SecurityCtx.GetUsername())
+		sc.SendForbiddenError(errors.New(sc.SecurityCtx.GetUsername()))
 		return
 	}
 }
@@ -49,7 +50,11 @@ func (sc *ScanAllAPI) Prepare() {
 //	}
 func (sc *ScanAllAPI) Post() {
 	ajr := models.AdminJobReq{}
-	sc.DecodeJSONReqAndValidate(&ajr)
+	isValid, err := sc.DecodeJSONReqAndValidate(&ajr)
+	if !isValid {
+		sc.SendBadRequestError(err)
+		return
+	}
 	ajr.Name = common_job.ImageScanAllJob
 	sc.submit(&ajr)
 	sc.Redirect(http.StatusCreated, strconv.FormatInt(ajr.ID, 10))
@@ -65,7 +70,11 @@ func (sc *ScanAllAPI) Post() {
 //	}
 func (sc *ScanAllAPI) Put() {
 	ajr := models.AdminJobReq{}
-	sc.DecodeJSONReqAndValidate(&ajr)
+	isValid, err := sc.DecodeJSONReqAndValidate(&ajr)
+	if !isValid {
+		sc.SendBadRequestError(err)
+		return
+	}
 	ajr.Name = common_job.ImageScanAllJob
 	sc.updateSchedule(ajr)
 }
diff --git a/src/core/api/scan_job.go b/src/core/api/scan_job.go
index 9489d57ed..446611ccf 100644
--- a/src/core/api/scan_job.go
+++ b/src/core/api/scan_job.go
@@ -16,11 +16,11 @@ package api
 
 import (
 	"github.com/goharbor/harbor/src/common/dao"
-	common_http "github.com/goharbor/harbor/src/common/http"
 	"github.com/goharbor/harbor/src/common/rbac"
 	"github.com/goharbor/harbor/src/common/utils/log"
 	"github.com/goharbor/harbor/src/core/utils"
 
+	"errors"
 	"fmt"
 	"net/http"
 	"strconv"
@@ -39,12 +39,12 @@ type ScanJobAPI struct {
 func (sj *ScanJobAPI) Prepare() {
 	sj.BaseController.Prepare()
 	if !sj.SecurityCtx.IsAuthenticated() {
-		sj.HandleUnauthorized()
+		sj.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 	id, err := sj.GetInt64FromPath(":id")
 	if err != nil {
-		sj.HandleBadRequest("invalid ID")
+		sj.SendBadRequestError(errors.New("invalid ID"))
 		return
 	}
 	sj.jobID = id
@@ -52,14 +52,16 @@ func (sj *ScanJobAPI) Prepare() {
 	data, err := dao.GetScanJob(id)
 	if err != nil {
 		log.Errorf("Failed to load job data for job: %d, error: %v", id, err)
-		sj.CustomAbort(http.StatusInternalServerError, "Failed to get Job data")
+		sj.SendInternalServerError(errors.New("Failed to get Job data"))
+		return
 	}
 	projectName := strings.SplitN(data.Repository, "/", 2)[0]
 
 	resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepositoryTagScanJob)
 	if !sj.SecurityCtx.Can(rbac.ActionRead, resource) {
 		log.Errorf("User does not have read permission for project: %s", projectName)
-		sj.HandleForbidden(sj.SecurityCtx.GetUsername())
+		sj.SendForbiddenError(errors.New(sj.SecurityCtx.GetUsername()))
+		return
 	}
 	sj.projectName = projectName
 	sj.jobUUID = data.UUID
@@ -69,20 +71,14 @@ func (sj *ScanJobAPI) Prepare() {
 func (sj *ScanJobAPI) GetLog() {
 	logBytes, err := utils.GetJobServiceClient().GetJobLog(sj.jobUUID)
 	if err != nil {
-		if httpErr, ok := err.(*common_http.Error); ok {
-			sj.RenderError(httpErr.Code, "")
-			log.Errorf(fmt.Sprintf("failed to get log of job %d: %d %s",
-				sj.jobID, httpErr.Code, httpErr.Message))
-			return
-		}
-		sj.HandleInternalServerError(fmt.Sprintf("Failed to get job logs, uuid: %s, error: %v", sj.jobUUID, err))
+		sj.ParseAndHandleError(fmt.Sprintf("Failed to get job logs, uuid: %s, error: %v", sj.jobUUID, err), err)
 		return
 	}
 	sj.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Length"), strconv.Itoa(len(logBytes)))
 	sj.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Type"), "text/plain")
 	_, err = sj.Ctx.ResponseWriter.Write(logBytes)
 	if err != nil {
-		sj.HandleInternalServerError(fmt.Sprintf("Failed to write job logs, uuid: %s, error: %v", sj.jobUUID, err))
+		sj.SendInternalServerError(fmt.Errorf("Failed to write job logs, uuid: %s, error: %v", sj.jobUUID, err))
 	}
 
 }
diff --git a/src/core/api/search.go b/src/core/api/search.go
index 107d20bae..4519de42e 100644
--- a/src/core/api/search.go
+++ b/src/core/api/search.go
@@ -16,7 +16,6 @@ package api
 
 import (
 	"fmt"
-	"net/http"
 	"strings"
 
 	"github.com/goharbor/harbor/src/common"
@@ -69,7 +68,7 @@ func (s *SearchAPI) Get() {
 		if isAuthenticated {
 			mys, err := s.SecurityCtx.GetMyProjects()
 			if err != nil {
-				s.HandleInternalServerError(fmt.Sprintf(
+				s.SendInternalServerError(fmt.Errorf(
 					"failed to get projects: %v", err))
 				return
 			}
@@ -111,7 +110,8 @@ func (s *SearchAPI) Get() {
 		})
 		if err != nil {
 			log.Errorf("failed to get total of repositories of project %d: %v", p.ProjectID, err)
-			s.CustomAbort(http.StatusInternalServerError, "")
+			s.SendInternalServerError(fmt.Errorf("failed to get total of repositories of project %d: %v", p.ProjectID, err))
+			return
 		}
 
 		p.RepoCount = total
@@ -122,7 +122,8 @@ func (s *SearchAPI) Get() {
 	repositoryResult, err := filterRepositories(projects, keyword)
 	if err != nil {
 		log.Errorf("failed to filter repositories: %v", err)
-		s.CustomAbort(http.StatusInternalServerError, "")
+		s.SendInternalServerError(fmt.Errorf("failed to filter repositories: %v", err))
+		return
 	}
 
 	result := &searchResult{
@@ -139,7 +140,9 @@ func (s *SearchAPI) Get() {
 		chartResults, err := searchHandler(keyword, proNames)
 		if err != nil {
 			log.Errorf("failed to filter charts: %v", err)
-			s.CustomAbort(http.StatusInternalServerError, err.Error())
+			s.SendInternalServerError(err)
+			return
+
 		}
 		result.Chart = &chartResults
 
diff --git a/src/core/api/statistic.go b/src/core/api/statistic.go
index 5311d1f4e..c1dfac152 100644
--- a/src/core/api/statistic.go
+++ b/src/core/api/statistic.go
@@ -15,8 +15,8 @@
 package api
 
 import (
+	"errors"
 	"fmt"
-	"net/http"
 
 	"github.com/goharbor/harbor/src/common/dao"
 	"github.com/goharbor/harbor/src/common/models"
@@ -48,7 +48,7 @@ type StatisticAPI struct {
 func (s *StatisticAPI) Prepare() {
 	s.BaseController.Prepare()
 	if !s.SecurityCtx.IsAuthenticated() {
-		s.HandleUnauthorized()
+		s.SendUnAuthorizedError(errors.New("UnAuthorized"))
 		return
 	}
 	s.username = s.SecurityCtx.GetUsername()
@@ -76,7 +76,8 @@ func (s *StatisticAPI) Get() {
 		})
 		if err != nil {
 			log.Errorf("failed to get total of public repositories: %v", err)
-			s.CustomAbort(http.StatusInternalServerError, "")
+			s.SendInternalServerError(fmt.Errorf("failed to get total of public repositories: %v", err))
+			return
 		}
 		statistic[PubRC] = n
 	}
@@ -85,7 +86,8 @@ func (s *StatisticAPI) Get() {
 		result, err := s.ProjectMgr.List(nil)
 		if err != nil {
 			log.Errorf("failed to get total of projects: %v", err)
-			s.CustomAbort(http.StatusInternalServerError, "")
+			s.SendInternalServerError(fmt.Errorf("failed to get total of projects: %v", err))
+			return
 		}
 		statistic[TPC] = result.Total
 		statistic[PriPC] = result.Total - statistic[PubPC]
@@ -93,7 +95,8 @@ func (s *StatisticAPI) Get() {
 		n, err := dao.GetTotalOfRepositories()
 		if err != nil {
 			log.Errorf("failed to get total of repositories: %v", err)
-			s.CustomAbort(http.StatusInternalServerError, "")
+			s.SendInternalServerError(fmt.Errorf("failed to get total of repositories: %v", err))
+			return
 		}
 		statistic[TRC] = n
 		statistic[PriRC] = n - statistic[PubRC]
@@ -124,7 +127,7 @@ func (s *StatisticAPI) Get() {
 				ProjectIDs: ids,
 			})
 			if err != nil {
-				s.HandleInternalServerError(fmt.Sprintf(
+				s.SendInternalServerError(fmt.Errorf(
 					"failed to get total of repositories for user %s: %v",
 					s.username, err))
 				return
diff --git a/src/core/api/systeminfo.go b/src/core/api/systeminfo.go
index 9f13ae0e6..e3abeb178 100644
--- a/src/core/api/systeminfo.go
+++ b/src/core/api/systeminfo.go
@@ -15,12 +15,14 @@
 package api
 
 import (
+	"errors"
 	"io/ioutil"
 	"net/http"
 	"os"
 	"strings"
 	"sync"
 
+	"fmt"
 	"github.com/goharbor/harbor/src/common"
 	"github.com/goharbor/harbor/src/common/dao"
 	clairdao "github.com/goharbor/harbor/src/common/dao/clair"
@@ -105,28 +107,24 @@ type GeneralInfo struct {
 	WithChartMuseum             bool                             `json:"with_chartmuseum"`
 }
 
-// validate for validating user if an admin.
-func (sia *SystemInfoAPI) validate() {
+// GetVolumeInfo gets specific volume storage info.
+func (sia *SystemInfoAPI) GetVolumeInfo() {
 	if !sia.SecurityCtx.IsAuthenticated() {
-		sia.HandleUnauthorized()
-		sia.StopRun()
+		sia.SendUnAuthorizedError(errors.New("UnAuthorized"))
+		return
 	}
 
 	if !sia.SecurityCtx.IsSysAdmin() {
-		sia.HandleForbidden(sia.SecurityCtx.GetUsername())
-		sia.StopRun()
+		sia.SendForbiddenError(errors.New(sia.SecurityCtx.GetUsername()))
+		return
 	}
-}
-
-// GetVolumeInfo gets specific volume storage info.
-func (sia *SystemInfoAPI) GetVolumeInfo() {
-	sia.validate()
 
 	systeminfo.Init()
 	capacity, err := imagestorage.GlobalDriver.Cap()
 	if err != nil {
 		log.Errorf("failed to get capacity: %v", err)
-		sia.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		sia.SendInternalServerError(fmt.Errorf("failed to get capacity: %v", err))
+		return
 	}
 	systemInfo := SystemInfo{
 		HarborStorage: Storage{
@@ -147,10 +145,12 @@ func (sia *SystemInfoAPI) GetCert() {
 		http.ServeFile(sia.Ctx.ResponseWriter, sia.Ctx.Request, defaultRootCert)
 	} else if os.IsNotExist(err) {
 		log.Error("No certificate found.")
-		sia.CustomAbort(http.StatusNotFound, "No certificate found.")
+		sia.SendNotFoundError(errors.New("no certificate found"))
+		return
 	} else {
 		log.Errorf("Unexpected error: %v", err)
-		sia.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		sia.SendInternalServerError(fmt.Errorf("Unexpected error: %v", err))
+		return
 	}
 }
 
@@ -159,7 +159,8 @@ func (sia *SystemInfoAPI) GetGeneralInfo() {
 	cfg, err := config.GetSystemCfg()
 	if err != nil {
 		log.Errorf("Error occurred getting config: %v", err)
-		sia.CustomAbort(http.StatusInternalServerError, "Unexpected error")
+		sia.SendInternalServerError(fmt.Errorf("Unexpected error: %v", err))
+		return
 	}
 	var registryURL string
 	if l := strings.Split(cfg[common.ExtEndpoint].(string), "://"); len(l) > 1 {
diff --git a/src/core/api/user.go b/src/core/api/user.go
index 765ed9da6..6c37a6d93 100644
--- a/src/core/api/user.go
+++ b/src/core/api/user.go
@@ -15,6 +15,7 @@
 package api
 
 import (
+	"errors"
 	"fmt"
 	"github.com/goharbor/harbor/src/common"
 	"github.com/goharbor/harbor/src/common/dao"
@@ -55,7 +56,8 @@ func (ua *UserAPI) Prepare() {
 	mode, err := config.AuthMode()
 	if err != nil {
 		log.Errorf("failed to get auth mode: %v", err)
-		ua.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		ua.SendInternalServerError(errors.New(""))
+		return
 	}
 
 	ua.AuthMode = mode
@@ -63,7 +65,8 @@ func (ua *UserAPI) Prepare() {
 	self, err := config.SelfRegistration()
 	if err != nil {
 		log.Errorf("failed to get self registration: %v", err)
-		ua.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
+		ua.SendInternalServerError(errors.New(""))
+		return
 	}
 
 	ua.SelfRegistration = self
@@ -72,7 +75,7 @@ func (ua *UserAPI) Prepare() {
 		if ua.Ctx.Input.IsPost() {
 			return
 		}
-		ua.HandleUnauthorized()
+		ua.SendUnAuthorizedError(errors.New("UnAuthorize"))
 		return
 	}
 
@@ -80,7 +83,7 @@ func (ua *UserAPI) Prepare() {
 		Username: ua.SecurityCtx.GetUsername(),
 	})
 	if err != nil {
-		ua.HandleInternalServerError(fmt.Sprintf("failed to get user %s: %v",
+		ua.SendInternalServerError(fmt.Errorf("failed to get user %s: %v",
 			ua.SecurityCtx.GetUsername(), err))
 		return
 	}
@@ -94,17 +97,20 @@ func (ua *UserAPI) Prepare() {
 		ua.userID, err = strconv.Atoi(id)
 		if err != nil {
 			log.Errorf("Invalid user id, error: %v", err)
-			ua.CustomAbort(http.StatusBadRequest, "Invalid user Id")
+			ua.SendBadRequestError(errors.New("invalid user Id"))
+			return
 		}
 		userQuery := models.User{UserID: ua.userID}
 		u, err := dao.GetUser(userQuery)
 		if err != nil {
 			log.Errorf("Error occurred in GetUser, error: %v", err)
-			ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
+			ua.SendInternalServerError(errors.New("internal error"))
+			return
 		}
 		if u == nil {
 			log.Errorf("User with Id: %d does not exist", ua.userID)
-			ua.CustomAbort(http.StatusNotFound, "")
+			ua.SendNotFoundError(errors.New(""))
+			return
 		}
 	}
 
@@ -118,19 +124,28 @@ func (ua *UserAPI) Get() {
 		u, err := dao.GetUser(userQuery)
 		if err != nil {
 			log.Errorf("Error occurred in GetUser, error: %v", err)
-			ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
+			ua.SendInternalServerError(err)
+			return
 		}
 		u.Password = ""
 		if ua.userID == ua.currentUserID {
 			u.HasAdminRole = ua.SecurityCtx.IsSysAdmin()
 		}
+		if ua.AuthMode == common.OIDCAuth {
+			o, err := ua.getOIDCUserInfo()
+			if err != nil {
+				ua.SendInternalServerError(err)
+				return
+			}
+			u.OIDCUserMeta = o
+		}
 		ua.Data["json"] = u
 		ua.ServeJSON()
 		return
 	}
 
 	log.Errorf("Current user, id: %d does not have admin role, can not view other user's detail", ua.currentUserID)
-	ua.RenderError(http.StatusForbidden, "User does not have admin role")
+	ua.SendForbiddenError(errors.New("user does not have admin role"))
 	return
 }
 
@@ -138,11 +153,16 @@ func (ua *UserAPI) Get() {
 func (ua *UserAPI) List() {
 	if !ua.IsAdmin {
 		log.Errorf("Current user, id: %d does not have admin role, can not list users", ua.currentUserID)
-		ua.RenderError(http.StatusForbidden, "User does not have admin role")
+		ua.SendForbiddenError(errors.New("user does not have admin role"))
+		return
+	}
+
+	page, size, err := ua.GetPaginationParams()
+	if err != nil {
+		ua.SendBadRequestError(err)
 		return
 	}
 
-	page, size := ua.GetPaginationParams()
 	query := &models.UserQuery{
 		Username: ua.GetString("username"),
 		Email:    ua.GetString("email"),
@@ -154,13 +174,13 @@ func (ua *UserAPI) List() {
 
 	total, err := dao.GetTotalOfUsers(query)
 	if err != nil {
-		ua.HandleInternalServerError(fmt.Sprintf("failed to get total of users: %v", err))
+		ua.SendInternalServerError(fmt.Errorf("failed to get total of users: %v", err))
 		return
 	}
 
 	users, err := dao.ListUsers(query)
 	if err != nil {
-		ua.HandleInternalServerError(fmt.Sprintf("failed to get users: %v", err))
+		ua.SendInternalServerError(fmt.Errorf("failed to get users: %v", err))
 		return
 	}
 
@@ -171,7 +191,11 @@ func (ua *UserAPI) List() {
 
 // Search ...
 func (ua *UserAPI) Search() {
-	page, size := ua.GetPaginationParams()
+	page, size, err := ua.GetPaginationParams()
+	if err != nil {
+		ua.SendBadRequestError(err)
+		return
+	}
 	query := &models.UserQuery{
 		Username: ua.GetString("username"),
 		Email:    ua.GetString("email"),
@@ -183,13 +207,13 @@ func (ua *UserAPI) Search() {
 
 	total, err := dao.GetTotalOfUsers(query)
 	if err != nil {
-		ua.HandleInternalServerError(fmt.Sprintf("failed to get total of users: %v", err))
+		ua.SendInternalServerError(fmt.Errorf("failed to get total of users: %v", err))
 		return
 	}
 
 	users, err := dao.ListUsers(query)
 	if err != nil {
-		ua.HandleInternalServerError(fmt.Sprintf("failed to get users: %v", err))
+		ua.SendInternalServerError(fmt.Errorf("failed to get users: %v", err))
 		return
 	}
 
@@ -206,42 +230,49 @@ func (ua *UserAPI) Search() {
 // Put ...
 func (ua *UserAPI) Put() {
 	if !ua.modifiable() {
-		ua.RenderError(http.StatusForbidden, fmt.Sprintf("User with ID %d cannot be modified", ua.userID))
+		ua.SendForbiddenError(fmt.Errorf("User with ID %d cannot be modified", ua.userID))
 		return
 	}
 	user := models.User{UserID: ua.userID}
-	ua.DecodeJSONReq(&user)
+	if err := ua.DecodeJSONReq(&user); err != nil {
+		ua.SendBadRequestError(err)
+		return
+	}
 	err := commonValidate(user)
 	if err != nil {
 		log.Warningf("Bad request in change user profile: %v", err)
-		ua.RenderError(http.StatusBadRequest, "change user profile error:"+err.Error())
+		ua.SendBadRequestError(fmt.Errorf("change user profile error:" + err.Error()))
 		return
 	}
 	userQuery := models.User{UserID: ua.userID}
 	u, err := dao.GetUser(userQuery)
 	if err != nil {
 		log.Errorf("Error occurred in GetUser, error: %v", err)
-		ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
+		ua.SendInternalServerError(errors.New("internal error"))
+		return
 	}
 	if u == nil {
 		log.Errorf("User with Id: %d does not exist", ua.userID)
-		ua.CustomAbort(http.StatusNotFound, "")
+		ua.SendNotFoundError(errors.New(""))
+		return
 	}
 	if u.Email != user.Email {
 		emailExist, err := dao.UserExists(user, "email")
 		if err != nil {
 			log.Errorf("Error occurred in change user profile: %v", err)
-			ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
+			ua.SendInternalServerError(errors.New("internal error"))
+			return
 		}
 		if emailExist {
 			log.Warning("email has already been used!")
-			ua.RenderError(http.StatusConflict, "email has already been used!")
+			ua.SendConflictError(errors.New("email has already been used"))
 			return
 		}
 	}
 	if err := dao.ChangeUserProfile(user); err != nil {
 		log.Errorf("Failed to update user profile, error: %v", err)
-		ua.CustomAbort(http.StatusInternalServerError, err.Error())
+		ua.SendInternalServerError(err)
+		return
 	}
 }
 
@@ -249,16 +280,21 @@ func (ua *UserAPI) Put() {
 func (ua *UserAPI) Post() {
 
 	if !(ua.AuthMode == common.DBAuth) {
-		ua.CustomAbort(http.StatusForbidden, "")
+		ua.SendForbiddenError(errors.New(""))
+		return
 	}
 
 	if !(ua.SelfRegistration || ua.IsAdmin) {
 		log.Warning("Registration can only be used by admin role user when self-registration is off.")
-		ua.CustomAbort(http.StatusForbidden, "")
+		ua.SendForbiddenError(errors.New(""))
+		return
 	}
 
 	user := models.User{}
-	ua.DecodeJSONReq(&user)
+	if err := ua.DecodeJSONReq(&user); err != nil {
+		ua.SendBadRequestError(err)
+		return
+	}
 	err := validate(user)
 	if err != nil {
 		log.Warningf("Bad request in Register: %v", err)
@@ -268,27 +304,30 @@ func (ua *UserAPI) Post() {
 	userExist, err := dao.UserExists(user, "username")
 	if err != nil {
 		log.Errorf("Error occurred in Register: %v", err)
-		ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
+		ua.SendInternalServerError(errors.New("internal error"))
+		return
 	}
 	if userExist {
 		log.Warning("username has already been used!")
-		ua.RenderError(http.StatusConflict, "username has already been used!")
+		ua.SendConflictError(errors.New("username has already been used"))
 		return
 	}
 	emailExist, err := dao.UserExists(user, "email")
 	if err != nil {
 		log.Errorf("Error occurred in change user profile: %v", err)
-		ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
+		ua.SendInternalServerError(errors.New("internal error"))
+		return
 	}
 	if emailExist {
 		log.Warning("email has already been used!")
-		ua.RenderError(http.StatusConflict, "email has already been used!")
+		ua.SendConflictError(errors.New("email has already been used"))
 		return
 	}
 	userID, err := dao.Register(user)
 	if err != nil {
 		log.Errorf("Error occurred in Register: %v", err)
-		ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
+		ua.SendInternalServerError(errors.New("internal error"))
+		return
 	}
 
 	ua.Redirect(http.StatusCreated, strconv.FormatInt(userID, 10))
@@ -297,7 +336,7 @@ func (ua *UserAPI) Post() {
 // Delete ...
 func (ua *UserAPI) Delete() {
 	if !ua.IsAdmin || ua.AuthMode != common.DBAuth || ua.userID == 1 || ua.currentUserID == ua.userID {
-		ua.RenderError(http.StatusForbidden, fmt.Sprintf("User with ID: %d cannot be removed, auth mode: %s, current user ID: %d", ua.userID, ua.AuthMode, ua.currentUserID))
+		ua.SendForbiddenError(fmt.Errorf("User with ID: %d cannot be removed, auth mode: %s, current user ID: %d", ua.userID, ua.AuthMode, ua.currentUserID))
 		return
 	}
 
@@ -305,7 +344,7 @@ func (ua *UserAPI) Delete() {
 	err = dao.DeleteUser(ua.userID)
 	if err != nil {
 		log.Errorf("Failed to delete data from database, error: %v", err)
-		ua.RenderError(http.StatusInternalServerError, "Failed to delete User")
+		ua.SendInternalServerError(errors.New("failed to delete User"))
 		return
 	}
 }
@@ -313,43 +352,46 @@ func (ua *UserAPI) Delete() {
 // ChangePassword handles PUT to /api/users/{}/password
 func (ua *UserAPI) ChangePassword() {
 	if !ua.modifiable() {
-		ua.RenderError(http.StatusForbidden, fmt.Sprintf("User with ID: %d is not modifiable", ua.userID))
+		ua.SendForbiddenError(fmt.Errorf("User with ID: %d is not modifiable", ua.userID))
 		return
 	}
 
 	changePwdOfOwn := ua.userID == ua.currentUserID
 
 	var req passwordReq
-	ua.DecodeJSONReq(&req)
+	if err := ua.DecodeJSONReq(&req); err != nil {
+		ua.SendBadRequestError(err)
+		return
+	}
 
 	if changePwdOfOwn && len(req.OldPassword) == 0 {
-		ua.HandleBadRequest("empty old_password")
+		ua.SendBadRequestError(errors.New("empty old_password"))
 		return
 	}
 
 	if len(req.NewPassword) == 0 {
-		ua.HandleBadRequest("empty new_password")
+		ua.SendBadRequestError(errors.New("empty new_password"))
 		return
 	}
 
 	user, err := dao.GetUser(models.User{UserID: ua.userID})
 	if err != nil {
-		ua.HandleInternalServerError(fmt.Sprintf("failed to get user %d: %v", ua.userID, err))
+		ua.SendInternalServerError(fmt.Errorf("failed to get user %d: %v", ua.userID, err))
 		return
 	}
 	if user == nil {
-		ua.HandleNotFound(fmt.Sprintf("user %d not found", ua.userID))
+		ua.SendNotFoundError(fmt.Errorf("user %d not found", ua.userID))
 		return
 	}
 	if changePwdOfOwn {
 		if user.Password != utils.Encrypt(req.OldPassword, user.Salt) {
 			log.Info("incorrect old_password")
-			ua.RenderError(http.StatusForbidden, "incorrect old_password")
+			ua.SendForbiddenError(errors.New("incorrect old_password"))
 			return
 		}
 	}
 	if user.Password == utils.Encrypt(req.NewPassword, user.Salt) {
-		ua.HandleBadRequest("the new password can not be same with the old one")
+		ua.SendBadRequestError(errors.New("the new password can not be same with the old one"))
 		return
 	}
 
@@ -358,7 +400,7 @@ func (ua *UserAPI) ChangePassword() {
 		Password: req.NewPassword,
 	}
 	if err = dao.ChangeUserPassword(updatedUser); err != nil {
-		ua.HandleInternalServerError(fmt.Sprintf("failed to change password of user %d: %v", ua.userID, err))
+		ua.SendInternalServerError(fmt.Errorf("failed to change password of user %d: %v", ua.userID, err))
 		return
 	}
 }
@@ -371,10 +413,14 @@ func (ua *UserAPI) ToggleUserAdminRole() {
 		return
 	}
 	userQuery := models.User{UserID: ua.userID}
-	ua.DecodeJSONReq(&userQuery)
+	if err := ua.DecodeJSONReq(&userQuery); err != nil {
+		ua.SendBadRequestError(err)
+		return
+	}
 	if err := dao.ToggleUserAdminRole(userQuery.UserID, userQuery.HasAdminRole); err != nil {
 		log.Errorf("Error occurred in ToggleUserAdminRole: %v", err)
-		ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
+		ua.SendInternalServerError(errors.New("internal error"))
+		return
 	}
 }
 
@@ -429,6 +475,25 @@ func (ua *UserAPI) ListUserPermissions() {
 	return
 }
 
+func (ua *UserAPI) getOIDCUserInfo() (*models.OIDCUser, error) {
+	key, err := config.SecretKey()
+	if err != nil {
+		return nil, err
+	}
+	o, err := dao.GetOIDCUserByUserID(ua.userID)
+	if err != nil || o == nil {
+		return nil, err
+	}
+	if len(o.Secret) > 0 {
+		p, err := utils.ReversibleDecrypt(o.Secret, key)
+		if err != nil {
+			return nil, err
+		}
+		o.PlainSecret = p
+	}
+	return o, nil
+}
+
 // modifiable returns whether the modify is allowed based on current auth mode and context
 func (ua *UserAPI) modifiable() bool {
 	if ua.AuthMode == common.DBAuth {
diff --git a/src/core/api/usergroup.go b/src/core/api/usergroup.go
index 36cca9705..dfb8edf7e 100644
--- a/src/core/api/usergroup.go
+++ b/src/core/api/usergroup.go
@@ -20,6 +20,7 @@ import (
 	"strconv"
 	"strings"
 
+	"errors"
 	"github.com/goharbor/harbor/src/common"
 	"github.com/goharbor/harbor/src/common/dao/group"
 	"github.com/goharbor/harbor/src/common/models"
@@ -42,7 +43,7 @@ const (
 func (uga *UserGroupAPI) Prepare() {
 	uga.BaseController.Prepare()
 	if !uga.SecurityCtx.IsAuthenticated() {
-		uga.HandleUnauthorized()
+		uga.SendUnAuthorizedError(errors.New("Unauthorized"))
 		return
 	}
 
@@ -51,13 +52,13 @@ func (uga *UserGroupAPI) Prepare() {
 		log.Warningf("failed to parse user group id, error: %v", err)
 	}
 	if ugid <= 0 && (uga.Ctx.Input.IsPut() || uga.Ctx.Input.IsDelete()) {
-		uga.HandleBadRequest(fmt.Sprintf("invalid user group ID: %s", uga.GetStringFromPath(":ugid")))
+		uga.SendBadRequestError(fmt.Errorf("invalid user group ID: %s", uga.GetStringFromPath(":ugid")))
 		return
 	}
 	uga.id = int(ugid)
 	// Common user can create/update, only harbor admin can delete user group.
 	if uga.Ctx.Input.IsDelete() && !uga.SecurityCtx.IsSysAdmin() {
-		uga.HandleForbidden(uga.SecurityCtx.GetUsername())
+		uga.SendForbiddenError(errors.New(uga.SecurityCtx.GetUsername()))
 		return
 	}
 }
@@ -71,7 +72,7 @@ func (uga *UserGroupAPI) Get() {
 		query := models.UserGroup{GroupType: common.LdapGroupType} // Current query LDAP group only
 		userGroupList, err := group.QueryUserGroup(query)
 		if err != nil {
-			uga.HandleInternalServerError(fmt.Sprintf("Failed to query database for user group list, error: %v", err))
+			uga.SendInternalServerError(fmt.Errorf("failed to query database for user group list, error: %v", err))
 			return
 		}
 		if len(userGroupList) > 0 {
@@ -81,11 +82,11 @@ func (uga *UserGroupAPI) Get() {
 		// return a specific user group
 		userGroup, err := group.GetUserGroup(ID)
 		if userGroup == nil {
-			uga.HandleNotFound("The user group does not exist.")
+			uga.SendNotFoundError(errors.New("the user group does not exist"))
 			return
 		}
 		if err != nil {
-			uga.HandleInternalServerError(fmt.Sprintf("Failed to query database for user group list, error: %v", err))
+			uga.SendInternalServerError(fmt.Errorf("failed to query database for user group list, error: %v", err))
 			return
 		}
 		uga.Data["json"] = userGroup
@@ -96,43 +97,47 @@ func (uga *UserGroupAPI) Get() {
 // Post ... Create User Group
 func (uga *UserGroupAPI) Post() {
 	userGroup := models.UserGroup{}
-	uga.DecodeJSONReq(&userGroup)
+	if err := uga.DecodeJSONReq(&userGroup); err != nil {
+		uga.SendBadRequestError(err)
+		return
+	}
+
 	userGroup.ID = 0
 	userGroup.GroupType = common.LdapGroupType
 	userGroup.LdapGroupDN = strings.TrimSpace(userGroup.LdapGroupDN)
 	userGroup.GroupName = strings.TrimSpace(userGroup.GroupName)
 	if len(userGroup.GroupName) == 0 {
-		uga.HandleBadRequest(userNameEmptyMsg)
+		uga.SendBadRequestError(errors.New(userNameEmptyMsg))
 		return
 	}
 	query := models.UserGroup{GroupType: userGroup.GroupType, LdapGroupDN: userGroup.LdapGroupDN}
 	result, err := group.QueryUserGroup(query)
 	if err != nil {
-		uga.HandleInternalServerError(fmt.Sprintf("Error occurred in add user group, error: %v", err))
+		uga.SendInternalServerError(fmt.Errorf("error occurred in add user group, error: %v", err))
 		return
 	}
 	if len(result) > 0 {
-		uga.HandleConflict("Error occurred in add user group, duplicate user group exist.")
+		uga.SendConflictError(errors.New("error occurred in add user group, duplicate user group exist"))
 		return
 	}
 	// User can not add ldap group when the ldap server is offline
 	ldapGroup, err := auth.SearchGroup(userGroup.LdapGroupDN)
 	if err == ldap.ErrNotFound || ldapGroup == nil {
-		uga.HandleNotFound(fmt.Sprintf("LDAP Group DN is not found: DN:%v", userGroup.LdapGroupDN))
+		uga.SendNotFoundError(fmt.Errorf("LDAP Group DN is not found: DN:%v", userGroup.LdapGroupDN))
 		return
 	}
 	if err == ldap.ErrDNSyntax {
-		uga.HandleBadRequest(fmt.Sprintf("Invalid DN syntax. DN: %v", userGroup.LdapGroupDN))
+		uga.SendBadRequestError(fmt.Errorf("invalid DN syntax. DN: %v", userGroup.LdapGroupDN))
 		return
 	}
 	if err != nil {
-		uga.HandleInternalServerError(fmt.Sprintf("Error occurred in search user group. error: %v", err))
+		uga.SendInternalServerError(fmt.Errorf("Error occurred in search user group. error: %v", err))
 		return
 	}
 
 	groupID, err := group.AddUserGroup(userGroup)
 	if err != nil {
-		uga.HandleInternalServerError(fmt.Sprintf("Error occurred in add user group, error: %v", err))
+		uga.SendInternalServerError(fmt.Errorf("Error occurred in add user group, error: %v", err))
 		return
 	}
 	uga.Redirect(http.StatusCreated, strconv.FormatInt(int64(groupID), 10))
@@ -141,18 +146,21 @@ func (uga *UserGroupAPI) Post() {
 // Put ... Only support update name
 func (uga *UserGroupAPI) Put() {
 	userGroup := models.UserGroup{}
-	uga.DecodeJSONReq(&userGroup)
+	if err := uga.DecodeJSONReq(&userGroup); err != nil {
+		uga.SendBadRequestError(err)
+		return
+	}
 	ID := uga.id
 	userGroup.GroupName = strings.TrimSpace(userGroup.GroupName)
 	if len(userGroup.GroupName) == 0 {
-		uga.HandleBadRequest(userNameEmptyMsg)
+		uga.SendBadRequestError(errors.New(userNameEmptyMsg))
 		return
 	}
 	userGroup.GroupType = common.LdapGroupType
 	log.Debugf("Updated user group %v", userGroup)
 	err := group.UpdateUserGroupName(ID, userGroup.GroupName)
 	if err != nil {
-		uga.HandleInternalServerError(fmt.Sprintf("Error occurred in update user group, error: %v", err))
+		uga.SendInternalServerError(fmt.Errorf("Error occurred in update user group, error: %v", err))
 		return
 	}
 	return
@@ -162,7 +170,7 @@ func (uga *UserGroupAPI) Put() {
 func (uga *UserGroupAPI) Delete() {
 	err := group.DeleteUserGroup(uga.id)
 	if err != nil {
-		uga.HandleInternalServerError(fmt.Sprintf("Error occurred in update user group, error: %v", err))
+		uga.SendInternalServerError(fmt.Errorf("Error occurred in update user group, error: %v", err))
 		return
 	}
 	return
diff --git a/src/core/auth/authproxy/auth.go b/src/core/auth/authproxy/auth.go
index a71786503..bfed1fe74 100644
--- a/src/core/auth/authproxy/auth.go
+++ b/src/core/auth/authproxy/auth.go
@@ -94,18 +94,11 @@ func (a *Auth) PostAuthenticate(u *models.User) error {
 	return a.OnBoardUser(u)
 }
 
-// SearchUser - TODO: Remove this workaround when #6767 is fixed.
-// When the flag is set it always return the default model without searching
+// SearchUser returns nil as authproxy does not have such capability.
+// When AlwaysOnboard is set it always return the default model.
 func (a *Auth) SearchUser(username string) (*models.User, error) {
-	a.ensure()
-	var queryCondition = models.User{
-		Username: username,
-	}
-	u, err := dao.GetUser(queryCondition)
-	if err != nil {
-		return nil, err
-	}
-	if a.AlwaysOnboard && u == nil {
+	var u *models.User
+	if a.AlwaysOnboard {
 		u = &models.User{Username: username}
 		if err := a.fillInModel(u); err != nil {
 			return nil, err
@@ -138,7 +131,7 @@ func (a *Auth) ensure() error {
 			return err
 		}
 		a.Endpoint = setting.Endpoint
-		a.SkipCertVerify = setting.SkipCertVerify
+		a.SkipCertVerify = !setting.VerifyCert
 		a.AlwaysOnboard = setting.AlwaysOnBoard
 	}
 	if a.client == nil {
diff --git a/src/core/config/config.go b/src/core/config/config.go
index 0b6049091..093552b4e 100644
--- a/src/core/config/config.go
+++ b/src/core/config/config.go
@@ -80,11 +80,14 @@ func Init() error {
 	return nil
 }
 
-// InitWithSettings init config with predefined configs
-func InitWithSettings(cfgs map[string]interface{}) {
+// InitWithSettings init config with predefined configs, and optionally overwrite the keyprovider
+func InitWithSettings(cfgs map[string]interface{}, kp ...comcfg.KeyProvider) {
 	Init()
 	cfgMgr = comcfg.NewInMemoryManager()
 	cfgMgr.UpdateConfig(cfgs)
+	if len(kp) > 0 {
+		keyProvider = kp[0]
+	}
 }
 
 func initKeyProvider() {
@@ -473,7 +476,7 @@ func HTTPAuthProxySetting() (*models.HTTPAuthProxy, error) {
 	return &models.HTTPAuthProxy{
 		Endpoint:            cfgMgr.Get(common.HTTPAuthProxyEndpoint).GetString(),
 		TokenReviewEndpoint: cfgMgr.Get(common.HTTPAuthProxyTokenReviewEndpoint).GetString(),
-		SkipCertVerify:      cfgMgr.Get(common.HTTPAuthProxySkipCertVerify).GetBool(),
+		VerifyCert:          cfgMgr.Get(common.HTTPAuthProxyVerifyCert).GetBool(),
 		AlwaysOnBoard:       cfgMgr.Get(common.HTTPAuthProxyAlwaysOnboard).GetBool(),
 	}, nil
 
@@ -493,12 +496,12 @@ func OIDCSetting() (*models.OIDCSetting, error) {
 	}
 
 	return &models.OIDCSetting{
-		Name:           cfgMgr.Get(common.OIDCName).GetString(),
-		Endpoint:       cfgMgr.Get(common.OIDCEndpoint).GetString(),
-		SkipCertVerify: cfgMgr.Get(common.OIDCSkipCertVerify).GetBool(),
-		ClientID:       cfgMgr.Get(common.OIDCCLientID).GetString(),
-		ClientSecret:   cfgMgr.Get(common.OIDCClientSecret).GetString(),
-		RedirectURL:    extEndpoint + common.OIDCCallbackPath,
-		Scope:          scope,
+		Name:         cfgMgr.Get(common.OIDCName).GetString(),
+		Endpoint:     cfgMgr.Get(common.OIDCEndpoint).GetString(),
+		VerifyCert:   cfgMgr.Get(common.OIDCVerifyCert).GetBool(),
+		ClientID:     cfgMgr.Get(common.OIDCCLientID).GetString(),
+		ClientSecret: cfgMgr.Get(common.OIDCClientSecret).GetString(),
+		RedirectURL:  extEndpoint + common.OIDCCallbackPath,
+		Scope:        scope,
 	}, nil
 }
diff --git a/src/core/config/config_test.go b/src/core/config/config_test.go
index be69533e4..89561778d 100644
--- a/src/core/config/config_test.go
+++ b/src/core/config/config_test.go
@@ -228,36 +228,36 @@ func TestConfigureValue_GetMap(t *testing.T) {
 
 func TestHTTPAuthProxySetting(t *testing.T) {
 	m := map[string]interface{}{
-		common.HTTPAuthProxyAlwaysOnboard:  "true",
-		common.HTTPAuthProxySkipCertVerify: "true",
-		common.HTTPAuthProxyEndpoint:       "https://auth.proxy/suffix",
+		common.HTTPAuthProxyAlwaysOnboard: "true",
+		common.HTTPAuthProxyVerifyCert:    "true",
+		common.HTTPAuthProxyEndpoint:      "https://auth.proxy/suffix",
 	}
 	InitWithSettings(m)
 	v, e := HTTPAuthProxySetting()
 	assert.Nil(t, e)
 	assert.Equal(t, *v, models.HTTPAuthProxy{
-		Endpoint:       "https://auth.proxy/suffix",
-		AlwaysOnBoard:  true,
-		SkipCertVerify: true,
+		Endpoint:      "https://auth.proxy/suffix",
+		AlwaysOnBoard: true,
+		VerifyCert:    true,
 	})
 }
 
 func TestOIDCSetting(t *testing.T) {
 	m := map[string]interface{}{
-		common.OIDCName:           "test",
-		common.OIDCEndpoint:       "https://oidc.test",
-		common.OIDCSkipCertVerify: "true",
-		common.OIDCScope:          "openid, profile",
-		common.OIDCCLientID:       "client",
-		common.OIDCClientSecret:   "secret",
-		common.ExtEndpoint:        "https://harbor.test",
+		common.OIDCName:         "test",
+		common.OIDCEndpoint:     "https://oidc.test",
+		common.OIDCVerifyCert:   "true",
+		common.OIDCScope:        "openid, profile",
+		common.OIDCCLientID:     "client",
+		common.OIDCClientSecret: "secret",
+		common.ExtEndpoint:      "https://harbor.test",
 	}
 	InitWithSettings(m)
 	v, e := OIDCSetting()
 	assert.Nil(t, e)
 	assert.Equal(t, "test", v.Name)
 	assert.Equal(t, "https://oidc.test", v.Endpoint)
-	assert.True(t, v.SkipCertVerify)
+	assert.True(t, v.VerifyCert)
 	assert.Equal(t, "client", v.ClientID)
 	assert.Equal(t, "secret", v.ClientSecret)
 	assert.Equal(t, "https://harbor.test/c/oidc/callback", v.RedirectURL)
diff --git a/src/core/controllers/base.go b/src/core/controllers/base.go
index 5d239ad65..88c9a7ba5 100644
--- a/src/core/controllers/base.go
+++ b/src/core/controllers/base.go
@@ -35,6 +35,8 @@ import (
 	"github.com/goharbor/harbor/src/core/config"
 )
 
+const userKey = "user"
+
 // CommonController handles request from UI that doesn't expect a page, such as /SwitchLanguage /logout ...
 type CommonController struct {
 	beego.Controller
@@ -69,7 +71,7 @@ func (cc *CommonController) Login() {
 	if user == nil {
 		cc.CustomAbort(http.StatusUnauthorized, "")
 	}
-	cc.SetSession("user", *user)
+	cc.SetSession(userKey, *user)
 }
 
 // LogOut Habor UI
diff --git a/src/core/controllers/oidc.go b/src/core/controllers/oidc.go
index be1c9ff84..c27501278 100644
--- a/src/core/controllers/oidc.go
+++ b/src/core/controllers/oidc.go
@@ -21,6 +21,7 @@ import (
 	"github.com/goharbor/harbor/src/common/dao"
 	"github.com/goharbor/harbor/src/common/models"
 	"github.com/goharbor/harbor/src/common/utils"
+	"github.com/goharbor/harbor/src/common/utils/log"
 	"github.com/goharbor/harbor/src/common/utils/oidc"
 	"github.com/goharbor/harbor/src/core/api"
 	"github.com/goharbor/harbor/src/core/config"
@@ -29,14 +30,19 @@ import (
 	"strings"
 )
 
-const idTokenKey = "oidc_id_token"
+const tokenKey = "oidc_token"
 const stateKey = "oidc_state"
+const userInfoKey = "oidc_user_info"
 
 // OIDCController handles requests for OIDC login, callback and user onboard
 type OIDCController struct {
 	api.BaseController
 }
 
+type onboardReq struct {
+	Username string `json:"username"`
+}
+
 type oidcUserData struct {
 	Issuer   string `json:"iss"`
 	Subject  string `json:"sub"`
@@ -47,7 +53,8 @@ type oidcUserData struct {
 // Prepare include public code path for call request handler of OIDCController
 func (oc *OIDCController) Prepare() {
 	if mode, _ := config.AuthMode(); mode != common.OIDCAuth {
-		oc.CustomAbort(http.StatusPreconditionFailed, fmt.Sprintf("Auth Mode: %s is not OIDC based.", mode))
+		oc.SendPreconditionFailedError(fmt.Errorf("Auth Mode: %s is not OIDC based", mode))
+		return
 	}
 }
 
@@ -56,7 +63,7 @@ func (oc *OIDCController) RedirectLogin() {
 	state := utils.GenerateRandomString()
 	url, err := oidc.AuthCodeURL(state)
 	if err != nil {
-		oc.RenderFormatedError(http.StatusInternalServerError, err)
+		oc.SendInternalServerError(err)
 		return
 	}
 	oc.SetSession(stateKey, state)
@@ -68,66 +75,101 @@ func (oc *OIDCController) RedirectLogin() {
 // kick off onboard if needed.
 func (oc *OIDCController) Callback() {
 	if oc.Ctx.Request.URL.Query().Get("state") != oc.GetSession(stateKey) {
-		oc.RenderError(http.StatusBadRequest, "State mismatch.")
+		oc.SendBadRequestError(errors.New("State mismatch"))
 		return
 	}
 	code := oc.Ctx.Request.URL.Query().Get("code")
 	ctx := oc.Ctx.Request.Context()
 	token, err := oidc.ExchangeToken(ctx, code)
 	if err != nil {
-		oc.RenderFormatedError(http.StatusInternalServerError, err)
+		oc.SendInternalServerError(err)
 		return
 	}
 	idToken, err := oidc.VerifyToken(ctx, token.IDToken)
 	if err != nil {
-		oc.RenderFormatedError(http.StatusInternalServerError, err)
+		oc.SendInternalServerError(err)
 		return
 	}
 	d := &oidcUserData{}
 	err = idToken.Claims(d)
 	if err != nil {
-		oc.RenderFormatedError(http.StatusInternalServerError, err)
+		oc.SendInternalServerError(err)
 		return
 	}
 	ouDataStr, err := json.Marshal(d)
 	if err != nil {
-		oc.RenderFormatedError(http.StatusInternalServerError, err)
+		oc.SendInternalServerError(err)
 		return
 	}
-	oc.SetSession(idTokenKey, string(ouDataStr))
-	// TODO: check and trigger onboard popup or redirect user to project page
-	oc.Data["json"] = d
-	oc.ServeFormatted()
+	u, err := dao.GetUserBySubIss(d.Subject, d.Issuer)
+	if err != nil {
+		oc.SendInternalServerError(err)
+		return
+	}
+	tokenBytes, err := json.Marshal(token)
+	if err != nil {
+		oc.SendInternalServerError(err)
+		return
+	}
+	oc.SetSession(tokenKey, tokenBytes)
+
+	if u == nil {
+		oc.SetSession(userInfoKey, string(ouDataStr))
+		oc.Controller.Redirect("/oidc-onboard", http.StatusFound)
+	} else {
+		oc.SetSession(userKey, *u)
+		oc.Controller.Redirect("/", http.StatusFound)
+	}
+
 }
 
 // Onboard handles the request to onboard an user authenticated via OIDC provider
 func (oc *OIDCController) Onboard() {
-
-	username := oc.GetString("username")
+	u := &onboardReq{}
+	if err := oc.DecodeJSONReq(u); err != nil {
+		oc.SendBadRequestError(err)
+		return
+	}
+	username := u.Username
 	if utils.IsIllegalLength(username, 1, 255) {
-		oc.RenderFormatedError(http.StatusBadRequest, errors.New("username with illegal length"))
+		oc.SendBadRequestError(errors.New("username with illegal length"))
 		return
 	}
 	if utils.IsContainIllegalChar(username, []string{",", "~", "#", "$", "%"}) {
-		oc.RenderFormatedError(http.StatusBadRequest, errors.New("username contains illegal characters"))
+		oc.SendBadRequestError(errors.New("username contains illegal characters"))
 		return
 	}
 
-	idTokenStr := oc.GetSession(idTokenKey)
-	d := &oidcUserData{}
-	err := json.Unmarshal([]byte(idTokenStr.(string)), &d)
+	userInfoStr, ok := oc.GetSession(userInfoKey).(string)
+	if !ok {
+		oc.SendBadRequestError(errors.New("Failed to get OIDC user info from session"))
+		return
+	}
+	log.Debugf("User info string: %s\n", userInfoStr)
+	tb, ok := oc.GetSession(tokenKey).([]byte)
+	if !ok {
+		oc.SendBadRequestError(errors.New("Failed to get OIDC token from session"))
+		return
+	}
+	s, t, err := secretAndToken(tb)
 	if err != nil {
-		oc.RenderFormatedError(http.StatusInternalServerError, err)
+		oc.SendInternalServerError(err)
+		return
+	}
+	d := &oidcUserData{}
+	err = json.Unmarshal([]byte(userInfoStr), &d)
+	if err != nil {
+		oc.SendInternalServerError(err)
 		return
 	}
 	oidcUser := models.OIDCUser{
 		SubIss: d.Subject + d.Issuer,
-		// TODO: get secret with secret manager.
-		Secret: utils.GenerateRandomString(),
+		Secret: s,
+		Token:  t,
 	}
 
-	var email string
-	if d.Email == "" {
+	email := d.Email
+	if email == "" {
 		email = utils.GenerateRandomString() + "@harbor.com"
 	}
 	user := models.User{
@@ -139,12 +181,32 @@ func (oc *OIDCController) Onboard() {
 	err = dao.OnBoardOIDCUser(&user)
 	if err != nil {
 		if strings.Contains(err.Error(), dao.ErrDupUser.Error()) {
-			oc.RenderFormatedError(http.StatusConflict, err)
+			oc.RenderError(http.StatusConflict, "Duplicate username")
 			return
 		}
-		oc.RenderFormatedError(http.StatusInternalServerError, err)
+		oc.SendInternalServerError(err)
+		oc.DelSession(userInfoKey)
 		return
 	}
 
-	oc.Controller.Redirect(config.GetPortalURL(), http.StatusMovedPermanently)
+	user.OIDCUserMeta = nil
+	oc.SetSession(userKey, user)
+	oc.DelSession(userInfoKey)
+}
+
+func secretAndToken(tokenBytes []byte) (string, string, error) {
+	key, err := config.SecretKey()
+	if err != nil {
+		return "", "", err
+	}
+	token, err := utils.ReversibleEncrypt((string)(tokenBytes), key)
+	if err != nil {
+		return "", "", err
+	}
+	str := utils.GenerateRandomString()
+	secret, err := utils.ReversibleEncrypt(str, key)
+	if err != nil {
+		return "", "", err
+	}
+	return secret, token, nil
 }
diff --git a/src/core/filter/security.go b/src/core/filter/security.go
index 9c95182fe..da9ecf9f6 100644
--- a/src/core/filter/security.go
+++ b/src/core/filter/security.go
@@ -17,6 +17,7 @@ package filter
 import (
 	"context"
 	"fmt"
+	"github.com/goharbor/harbor/src/common/utils/oidc"
 	"net/http"
 	"regexp"
 
@@ -110,6 +111,7 @@ func Init() {
 	// standalone
 	reqCtxModifiers = []ReqCtxModifier{
 		&secretReqCtxModifier{config.SecretStore},
+		&oidcCliReqCtxModifier{},
 		&authProxyReqCtxModifier{},
 		&robotAuthReqCtxModifier{},
 		&basicAuthReqCtxModifier{},
@@ -205,6 +207,47 @@ func (r *robotAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
 	return true
 }
 
+type oidcCliReqCtxModifier struct{}
+
+func (oc *oidcCliReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
+	path := ctx.Request.URL.Path
+	if path != "/service/token" || strings.HasPrefix(path, "/chartrepo/") {
+		log.Debug("OIDC CLI modifer only handles request by docker CLI or helm CLI")
+		return false
+	}
+	authMode, err := config.AuthMode()
+	if err != nil {
+		log.Errorf("fail to get auth mode, %v", err)
+		return false
+	}
+	if authMode != common.OIDCAuth {
+		return false
+	}
+	username, secret, ok := ctx.Request.BasicAuth()
+	if !ok {
+		return false
+	}
+
+	user, err := dao.GetUser(models.User{
+		Username: username,
+	})
+	if err != nil {
+		log.Errorf("Failed to get user: %v", err)
+		return false
+	}
+	if user == nil {
+		return false
+	}
+	if err := oidc.VerifySecret(ctx.Request.Context(), user.UserID, secret); err != nil {
+		log.Errorf("Failed to verify secret: %v", err)
+		return false
+	}
+	pm := config.GlobalProjectMgr
+	sc := local.NewSecurityContext(user, pm)
+	setSecurCtxAndPM(ctx.Request, sc, pm)
+	return true
+}
+
 type authProxyReqCtxModifier struct{}
 
 func (ap *authProxyReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
@@ -249,7 +292,7 @@ func (ap *authProxyReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
 		},
 		BearerToken: proxyPwd,
 		TLSClientConfig: rest.TLSClientConfig{
-			Insecure: httpAuthProxyConf.SkipCertVerify,
+			Insecure: !httpAuthProxyConf.VerifyCert,
 		},
 	}
 	authClient, err := rest.RESTClientFor(authClientCfg)
@@ -307,7 +350,6 @@ func (ap *authProxyReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
 		return false
 	}
 
-	log.Debug("using local database project manager")
 	pm := config.GlobalProjectMgr
 	log.Debug("creating local database security context for auth proxy...")
 	securCtx := local.NewSecurityContext(user, pm)
diff --git a/src/core/filter/security_test.go b/src/core/filter/security_test.go
index 91e2d31a7..bce914c6c 100644
--- a/src/core/filter/security_test.go
+++ b/src/core/filter/security_test.go
@@ -16,6 +16,8 @@ package filter
 
 import (
 	"context"
+	"github.com/goharbor/harbor/src/common/utils/oidc"
+	"github.com/stretchr/testify/require"
 	"log"
 	"net/http"
 	"net/http/httptest"
@@ -28,6 +30,7 @@ import (
 	"github.com/astaxie/beego"
 	beegoctx "github.com/astaxie/beego/context"
 	"github.com/astaxie/beego/session"
+	config2 "github.com/goharbor/harbor/src/common/config"
 	"github.com/goharbor/harbor/src/common/dao"
 	"github.com/goharbor/harbor/src/common/models"
 	commonsecret "github.com/goharbor/harbor/src/common/secret"
@@ -118,6 +121,53 @@ func TestSecretReqCtxModifier(t *testing.T) {
 	assert.NotNil(t, projectManager(ctx))
 }
 
+func TestOIDCCliReqCtxModifier(t *testing.T) {
+	conf := map[string]interface{}{
+		common.AUTHMode:         common.OIDCAuth,
+		common.OIDCName:         "test",
+		common.OIDCEndpoint:     "https://accounts.google.com",
+		common.OIDCVerifyCert:   "true",
+		common.OIDCScope:        "openid, profile, offline_access",
+		common.OIDCCLientID:     "client",
+		common.OIDCClientSecret: "secret",
+		common.ExtEndpoint:      "https://harbor.test",
+	}
+
+	kp := &config2.PresetKeyProvider{Key: "naa4JtarA1Zsc3uY"}
+	config.InitWithSettings(conf, kp)
+
+	modifier := &oidcCliReqCtxModifier{}
+	req1, err := http.NewRequest(http.MethodGet,
+		"http://127.0.0.1/api/projects/", nil)
+	require.Nil(t, err)
+	ctx1, err := newContext(req1)
+	require.Nil(t, err)
+	assert.False(t, modifier.Modify(ctx1))
+	req2, err := http.NewRequest(http.MethodGet, "http://127.0.0.1/service/token", nil)
+	require.Nil(t, err)
+	ctx2, err := newContext(req2)
+	require.Nil(t, err)
+	assert.False(t, modifier.Modify(ctx2))
+	username := "oidcModiferTester"
+	password := "oidcSecret"
+	u := &models.User{
+		Username: username,
+		Email:    "testtest@test.org",
+		Password: "12345678",
+	}
+	id, err := dao.Register(*u)
+	require.Nil(t, err)
+	oidc.SetHardcodeVerifierForTest(password)
+	req3, err := http.NewRequest(http.MethodGet, "http://127.0.0.1/service/token", nil)
+	require.Nil(t, err)
+	req3.SetBasicAuth(username, password)
+	ctx3, err := newContext(req3)
+	assert.True(t, modifier.Modify(ctx3))
+	o := dao.GetOrmer()
+	_, err = o.Delete(&models.User{UserID: int(id)})
+	assert.Nil(t, err)
+}
+
 func TestRobotReqCtxModifier(t *testing.T) {
 	req, err := http.NewRequest(http.MethodGet,
 		"http://127.0.0.1/api/projects/", nil)
@@ -135,7 +185,7 @@ func TestRobotReqCtxModifier(t *testing.T) {
 	assert.False(t, modified)
 }
 
-func TestAutoProxyReqCtxModifier(t *testing.T) {
+func TestAuthProxyReqCtxModifier(t *testing.T) {
 
 	server, err := fiter_test.NewAuthProxyTestServer()
 	assert.Nil(t, err)
@@ -143,7 +193,7 @@ func TestAutoProxyReqCtxModifier(t *testing.T) {
 
 	c := map[string]interface{}{
 		common.HTTPAuthProxyAlwaysOnboard:       "true",
-		common.HTTPAuthProxySkipCertVerify:      "true",
+		common.HTTPAuthProxyVerifyCert:          "false",
 		common.HTTPAuthProxyEndpoint:            "https://auth.proxy/suffix",
 		common.HTTPAuthProxyTokenReviewEndpoint: server.URL,
 		common.AUTHMode:                         common.HTTPAuth,
@@ -155,7 +205,7 @@ func TestAutoProxyReqCtxModifier(t *testing.T) {
 	assert.Equal(t, *v, models.HTTPAuthProxy{
 		Endpoint:            "https://auth.proxy/suffix",
 		AlwaysOnBoard:       true,
-		SkipCertVerify:      true,
+		VerifyCert:          false,
 		TokenReviewEndpoint: server.URL,
 	})
 
diff --git a/src/core/service/notifications/admin/handler.go b/src/core/service/notifications/admin/handler.go
index b93d24d93..310b8926e 100644
--- a/src/core/service/notifications/admin/handler.go
+++ b/src/core/service/notifications/admin/handler.go
@@ -75,12 +75,12 @@ func (h *Handler) HandleAdminJob() {
 	log.Infof("received admin job status update event: job-%d, status-%s", h.id, h.status)
 	// create the mapping relationship between the jobs in database and jobservice
 	if err := dao.SetAdminJobUUID(h.id, h.UUID); err != nil {
-		h.HandleInternalServerError(err.Error())
+		h.SendInternalServerError(err)
 		return
 	}
 	if err := dao.UpdateAdminJobStatus(h.id, h.status); err != nil {
 		log.Errorf("Failed to update job status, id: %d, status: %s", h.id, h.status)
-		h.HandleInternalServerError(err.Error())
+		h.SendInternalServerError(err)
 		return
 	}
 }
diff --git a/src/core/service/notifications/jobs/handler.go b/src/core/service/notifications/jobs/handler.go
index 138d70f73..2ddf06b27 100644
--- a/src/core/service/notifications/jobs/handler.go
+++ b/src/core/service/notifications/jobs/handler.go
@@ -78,7 +78,7 @@ func (h *Handler) HandleScan() {
 	log.Debugf("received san job status update event: job-%d, status-%s", h.id, h.status)
 	if err := dao.UpdateScanJobStatus(h.id, h.status); err != nil {
 		log.Errorf("Failed to update job status, id: %d, status: %s", h.id, h.status)
-		h.HandleInternalServerError(err.Error())
+		h.SendInternalServerError(err)
 		return
 	}
 }
@@ -88,7 +88,7 @@ func (h *Handler) HandleReplicationScheduleJob() {
 	log.Debugf("received replication schedule job status update event: schedule-job-%d, status-%s", h.id, h.status)
 	if err := scheduler.UpdateStatus(h.id, h.status); err != nil {
 		log.Errorf("Failed to update job status, id: %d, status: %s", h.id, h.status)
-		h.HandleInternalServerError(err.Error())
+		h.SendInternalServerError(err)
 		return
 	}
 }
@@ -98,7 +98,7 @@ func (h *Handler) HandleReplicationTask() {
 	log.Debugf("received replication task status update event: task-%d, status-%s", h.id, h.status)
 	if err := hook.UpdateTask(replication.OperationCtl, h.id, h.rawStatus); err != nil {
 		log.Errorf("Failed to update replication task status, id: %d, status: %s", h.id, h.status)
-		h.HandleInternalServerError(err.Error())
+		h.SendInternalServerError(err)
 		return
 	}
 }
diff --git a/src/core/utils/error.go b/src/core/utils/error.go
deleted file mode 100644
index 62121f6b3..000000000
--- a/src/core/utils/error.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package utils
-
-import (
-	"encoding/json"
-	"errors"
-)
-
-// WrapErrorMessage wraps the error msg to the well formated error message `{ "error": "The error message" }`
-func WrapErrorMessage(msg string) string {
-	errBody := make(map[string]string, 1)
-	errBody["error"] = msg
-	data, err := json.Marshal(&errBody)
-	if err != nil {
-		return msg
-	}
-
-	return string(data)
-}
-
-// WrapError wraps the error to the well formated error `{ "error": "The error message" }`
-func WrapError(err error) error {
-	if err == nil {
-		return nil
-	}
-
-	return errors.New(WrapErrorMessage(err.Error()))
-}
diff --git a/src/core/utils/error_test.go b/src/core/utils/error_test.go
deleted file mode 100644
index 8e5c7d673..000000000
--- a/src/core/utils/error_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package utils
-
-import (
-	"encoding/json"
-	"errors"
-	"testing"
-)
-
-// Test case for error wrapping function.
-func TestWrapError(t *testing.T) {
-	if WrapError(nil) != nil {
-		t.Fatal("expect nil error but got a non-nil one")
-	}
-
-	err := errors.New("mock error")
-	formatedErr := WrapError(err)
-	if formatedErr == nil {
-		t.Fatal("expect non-nil error but got nil")
-	}
-
-	jsonErr := formatedErr.Error()
-	structuredErr := make(map[string]string, 1)
-	if e := json.Unmarshal([]byte(jsonErr), &structuredErr); e != nil {
-		t.Fatal("expect nil error but got a non-nil one when doing error converting")
-	}
-	if msg, ok := structuredErr["error"]; !ok {
-		t.Fatal("expect an 'error' filed but missing")
-	} else {
-		if msg != "mock error" {
-			t.Fatalf("expect error message '%s' but got '%s'", "mock error", msg)
-		}
-	}
-}
diff --git a/src/portal/lib/src/config/config.ts b/src/portal/lib/src/config/config.ts
index 0c5c78463..cd2193bb1 100644
--- a/src/portal/lib/src/config/config.ts
+++ b/src/portal/lib/src/config/config.ts
@@ -89,13 +89,14 @@ export class Configuration {
     scan_all_policy: ComplexValueItem;
     read_only: BoolValueItem;
     http_authproxy_endpoint?: StringValueItem;
-    http_authproxy_skip_cert_verify?: BoolValueItem;
+    http_authproxy_tokenreview_endpoint?: StringValueItem;
+    http_authproxy_verify_cert?: BoolValueItem;
     http_authproxy_always_onboard?: BoolValueItem;
     oidc_name?: StringValueItem;
     oidc_endpoint?: StringValueItem;
     oidc_client_id?: StringValueItem;
     oidc_client_secret?: StringValueItem;
-    oidc_skip_cert_verify?: BoolValueItem;
+    oidc_verify_cert?: BoolValueItem;
     oidc_scope?: StringValueItem;
     public constructor() {
         this.auth_mode = new StringValueItem("db_auth", true);
@@ -139,13 +140,14 @@ export class Configuration {
         }, true);
         this.read_only = new BoolValueItem(false, true);
         this.http_authproxy_endpoint = new StringValueItem("", true);
-        this.http_authproxy_skip_cert_verify = new BoolValueItem(false, true);
+        this.http_authproxy_tokenreview_endpoint = new StringValueItem("", true);
+        this.http_authproxy_verify_cert = new BoolValueItem(false, true);
         this.http_authproxy_always_onboard = new BoolValueItem(false, true);
         this.oidc_name = new StringValueItem('', true);
         this.oidc_endpoint = new StringValueItem('', true);
         this.oidc_client_id = new StringValueItem('', true);
         this.oidc_client_secret = new StringValueItem('', true);
-        this.oidc_skip_cert_verify = new BoolValueItem(false, true);
+        this.oidc_verify_cert = new BoolValueItem(false, true);
         this.oidc_scope = new StringValueItem('', true);
     }
 }
diff --git a/src/portal/lib/src/cron-schedule/cron-schedule.component.html b/src/portal/lib/src/cron-schedule/cron-schedule.component.html
index 9f6fcdbe9..5faa13f6e 100644
--- a/src/portal/lib/src/cron-schedule/cron-schedule.component.html
+++ b/src/portal/lib/src/cron-schedule/cron-schedule.component.html
@@ -33,7 +33,7 @@
     </select>
   </div>
   <span [hidden]="scheduleType!==SCHEDULE_TYPE.CUSTOM">{{ "SCHEDULE.CRON" | translate }} :</span>
-  <div [hidden]="scheduleType!==SCHEDULE_TYPE.CUSTOM">
+  <div [hidden]="scheduleType!==SCHEDULE_TYPE.CUSTOM" class="cron-input">
     <label for="targetCron" aria-haspopup="true" role="tooltip" [class.invalid]="dateInvalid" class="tooltip tooltip-validation tooltip-md tooltip-top-right cron-label">
       <input type="text"  (blur)="blurInvalid()" (input)="inputInvalid()" name=targetCron id="targetCron" #cronStringInput="ngModel" required class="form-control"
         [(ngModel)]="cronString">
diff --git a/src/portal/lib/src/cron-schedule/cron-schedule.component.scss b/src/portal/lib/src/cron-schedule/cron-schedule.component.scss
index 0600f58c5..cf15c1f9e 100644
--- a/src/portal/lib/src/cron-schedule/cron-schedule.component.scss
+++ b/src/portal/lib/src/cron-schedule/cron-schedule.component.scss
@@ -37,6 +37,12 @@
       width: 100px;
     }
 
+    .cron-input {
+      position: absolute;
+      display: inline-block;
+      width: 270px;
+    }
+
     .cron-label {
       width: 195px;
     }
diff --git a/src/portal/lib/src/repository-gridview/repository-gridview.component.spec.ts b/src/portal/lib/src/repository-gridview/repository-gridview.component.spec.ts
index 999dfd2f1..658cc1b25 100644
--- a/src/portal/lib/src/repository-gridview/repository-gridview.component.spec.ts
+++ b/src/portal/lib/src/repository-gridview/repository-gridview.component.spec.ts
@@ -198,7 +198,7 @@ describe('RepositoryComponentGridview (inline template)', () => {
     });
   }));
   // Will fail after upgrade to angular 6. todo: need to fix it.
-  xit('should filter data by keyword', async(() => {
+  it('should filter data by keyword', async(() => {
     fixtureRepo.whenStable().then(() => {
       fixtureRepo.detectChanges();
 
diff --git a/src/portal/src/app/account/account-settings/account-settings-modal.component.html b/src/portal/src/app/account/account-settings/account-settings-modal.component.html
index b4d4a6d92..f9a32e790 100644
--- a/src/portal/src/app/account/account-settings/account-settings-modal.component.html
+++ b/src/portal/src/app/account/account-settings/account-settings-modal.component.html
@@ -51,6 +51,19 @@
                     </span>
                     </label>
                 </div>
+                <div class="form-group form-group-override" *ngIf="account.oidc_user_meta">
+                    <label for="cli_password" aria-haspopup="true" class="form-group-label-override"><span class="label-inner-text">{{'PROFILE.CLI_PASSWORD' | translate}}</span>
+                        <clr-tooltip>
+                            <clr-icon clrTooltipTrigger shape="info-circle" size="20"></clr-icon>
+                            <clr-tooltip-content clrPosition="top-right" clrSize="md" *clrIfOpen>
+                                <span> {{'PROFILE.CLI_PASSWORD_TIP' | translate}}</span>
+                            </clr-tooltip-content>
+                        </clr-tooltip></label>
+                    <input type="password" name="cli_password" disabled [ngModel]="'account.oidc_user_meta.secret'" size="33">
+                    <div class="rename-tool">
+                        <hbr-copy-input #copyInput (onCopySuccess)="onSuccess($event)" (onCopyError)="onCpError($event)"  iconMode="true" defaultValue="{{account.oidc_user_meta.secret}}"></hbr-copy-input>
+                    </div>
+                </div>
             </section>
         </form>
     </div>
diff --git a/src/portal/src/app/account/account-settings/account-settings-modal.component.scss b/src/portal/src/app/account/account-settings/account-settings-modal.component.scss
index ee71c0347..c0ea6022d 100644
--- a/src/portal/src/app/account/account-settings/account-settings-modal.component.scss
+++ b/src/portal/src/app/account/account-settings/account-settings-modal.component.scss
@@ -11,4 +11,7 @@ clr-modal {
     position: relative;
     bottom: 9px;
     }
+    .label-inner-text{
+        margin: 0;
+    }
 }
\ No newline at end of file
diff --git a/src/portal/src/app/account/account-settings/account-settings-modal.component.ts b/src/portal/src/app/account/account-settings/account-settings-modal.component.ts
index 0bdb211fb..2f81b64ed 100644
--- a/src/portal/src/app/account/account-settings/account-settings-modal.component.ts
+++ b/src/portal/src/app/account/account-settings/account-settings-modal.component.ts
@@ -22,7 +22,7 @@ import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.com
 import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
 import { SearchTriggerService } from "../../base/global-search/search-trigger.service";
 import { CommonRoutes } from "../../shared/shared.const";
-
+import { CopyInputComponent } from "@harbor/ui";
 @Component({
   selector: "account-settings-modal",
   templateUrl: "account-settings-modal.component.html",
@@ -48,6 +48,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
   accountFormRef: NgForm;
   @ViewChild("accountSettingsFrom") accountForm: NgForm;
   @ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent;
+  @ViewChild("copyInput") copyInput: CopyInputComponent;
 
   constructor(
     private session: SessionService,
@@ -320,4 +321,10 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
     this.inlineAlert.close();
     this.opened = false;
   }
+  onSuccess(event) {
+    this.inlineAlert.showInlineSuccess({message: 'PROFILE.COPY_SUCCESS'});
+  }
+  onError(event) {
+    this.inlineAlert.showInlineError({message: 'PROFILE.COPY_ERROR'});
+  }
 }
diff --git a/src/portal/src/app/account/account.module.ts b/src/portal/src/app/account/account.module.ts
index 13d8e0e37..74662a609 100644
--- a/src/portal/src/app/account/account.module.ts
+++ b/src/portal/src/app/account/account.module.ts
@@ -18,7 +18,6 @@ import { CoreModule } from '../core/core.module';
 import { SharedModule } from '../shared/shared.module';
 import { RepositoryModule } from '../repository/repository.module';
 
-import { SignInComponent } from './sign-in/sign-in.component';
 import { PasswordSettingComponent } from './password-setting/password-setting.component';
 import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
 import { SignUpComponent } from './sign-up/sign-up.component';
@@ -36,7 +35,6 @@ import { PasswordSettingService } from './password-setting/password-setting.serv
     RepositoryModule
   ],
   declarations: [
-    SignInComponent,
     PasswordSettingComponent,
     AccountSettingsModalComponent,
     SignUpComponent,
@@ -44,10 +42,11 @@ import { PasswordSettingService } from './password-setting/password-setting.serv
     ResetPasswordComponent,
     SignUpPageComponent],
   exports: [
-    SignInComponent,
     PasswordSettingComponent,
     AccountSettingsModalComponent,
+    ForgotPasswordComponent,
     ResetPasswordComponent,
+    SignUpComponent,
     SignUpPageComponent],
 
   providers: [PasswordSettingService]
diff --git a/src/portal/src/app/app.module.ts b/src/portal/src/app/app.module.ts
index 62f8bf3c2..d3a0ec54e 100644
--- a/src/portal/src/app/app.module.ts
+++ b/src/portal/src/app/app.module.ts
@@ -19,6 +19,7 @@ import { BaseModule } from './base/base.module';
 import { HarborRoutingModule } from './harbor-routing.module';
 import { SharedModule } from './shared/shared.module';
 import { AccountModule } from './account/account.module';
+import { SignInModule } from './sign-in/sign-in.module';
 import { ConfigurationModule } from './config/config.module';
 import { DeveloperCenterModule } from './dev-center/dev-center.module';
 import { registerLocaleData } from '@angular/common';
@@ -63,6 +64,7 @@ export function getCurrentLanguage(translateService: TranslateService) {
         SharedModule,
         BaseModule,
         AccountModule,
+        SignInModule,
         HarborRoutingModule,
         ConfigurationModule,
         DeveloperCenterModule,
diff --git a/src/portal/src/app/base/base.module.ts b/src/portal/src/app/base/base.module.ts
index a6c13abfc..4c3a15f3e 100644
--- a/src/portal/src/app/base/base.module.ts
+++ b/src/portal/src/app/base/base.module.ts
@@ -46,7 +46,7 @@ import { SearchTriggerService } from './global-search/search-trigger.service';
     HarborShellComponent,
     SearchResultComponent,
   ],
-  exports: [ HarborShellComponent ],
+  exports: [ HarborShellComponent, NavigatorComponent, SearchResultComponent ],
   providers: [SearchTriggerService]
 })
 export class BaseModule {
diff --git a/src/portal/src/app/config/auth/config-auth.component.html b/src/portal/src/app/config/auth/config-auth.component.html
index 665e1d5e7..a835415f2 100644
--- a/src/portal/src/app/config/auth/config-auth.component.html
+++ b/src/portal/src/app/config/auth/config-auth.component.html
@@ -287,13 +287,26 @@
             </label>
         </div>
         <div class="form-group">
-            <label for="http_authproxy_skip_cert_verify"
+            <label for="http_authproxy_tokenreview_endpoint" class="required">{{'CONFIG.HTTP_AUTH.TOKEN_REVIEW' | translate}}</label>
+            <label for="http_authproxy_tokenreview_endpoint" aria-haspopup="true" role="tooltip"
+                class="tooltip tooltip-validation tooltip-md tooltip-top-right"
+                [class.invalid]="httpAuthproxyTokenReviewInput.invalid && (httpAuthproxyTokenReviewInput.dirty || httpAuthproxyTokenReviewInput.touched)">
+                <input type="text" #httpAuthproxyTokenReviewInput="ngModel" pattern="^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(.*?)*$"
+                 required id="http_authproxy_tokenreview_endpoint" name="http_authproxy_tokenreview_endpoint" size="35"
+                 [(ngModel)]="currentConfig.http_authproxy_tokenreview_endpoint.value" [disabled]="!currentConfig.http_authproxy_tokenreview_endpoint.editable">
+                <span class="tooltip-content">
+                    {{'TOOLTIP.ENDPOINT_FORMAT' | translate}}
+                </span>
+            </label>
+        </div>
+        <div class="form-group">
+            <label for="http_authproxy_verify_cert"
                 class="required">{{'CONFIG.HTTP_AUTH.VERIFY_CERT' | translate}}</label>
             <clr-checkbox-wrapper>
-                <input type="checkbox" clrCheckbox name="http_authproxy_skip_cert_verify"
-                    id="http_authproxy_skip_cert_verify"
-                    [(ngModel)]="currentConfig.http_authproxy_skip_cert_verify.value"
-                    [disabled]="!currentConfig.http_authproxy_skip_cert_verify.editable" />
+                <input type="checkbox" clrCheckbox name="http_authproxy_verify_cert"
+                    id="http_authproxy_verify_cert"
+                    [(ngModel)]="currentConfig.http_authproxy_verify_cert.value"
+                    [disabled]="!currentConfig.http_authproxy_verify_cert.editable" />
             </clr-checkbox-wrapper>
         </div>
         <div class="form-group">
@@ -390,16 +403,16 @@
             </a>
         </div>
         <div class="form-group">
-            <label for="oidc_skip_cert_verify">{{'CONFIG.OIDC.OIDCSKIPCERTVERIFY' | translate}}</label>
+            <label for="oidc_verify_cert">{{'CONFIG.OIDC.OIDC_VERIFYCERT' | translate}}</label>
             <clr-checkbox-wrapper>
-                <input type="checkbox" clrCheckbox name="oidc_skip_cert_verify" id="oidc_skip_cert_verify"
-                    [disabled]="disabled(currentConfig.oidc_skip_cert_verify)"
-                    [(ngModel)]="currentConfig.oidc_skip_cert_verify.value" />
+                <input type="checkbox" clrCheckbox name="oidc_verify_cert" id="oidc_verify_cert"
+                    [disabled]="disabled(currentConfig.oidc_verify_cert)"
+                    [(ngModel)]="currentConfig.oidc_verify_cert.value" />
             </clr-checkbox-wrapper>
             <a href="javascript:void(0)" role="tooltip" aria-haspopup="true"
                 class="tooltip tooltip-lg tooltip-top-right top-1px">
                 <clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
-                <span class="tooltip-content">{{'TOOLTIP.OIDC_SKIPCERTVERIFY' | translate}}</span>
+                <span class="tooltip-content">{{'TOOLTIP.OIDC_VERIFYCERT' | translate}}</span>
             </a>
         </div>
     </section>
@@ -412,4 +425,4 @@
     <button type="button" class="btn btn-outline" (click)="testLDAPServer()" *ngIf="showLdapServerBtn"
         [disabled]="!isLDAPConfigValid()">{{'BUTTON.TEST_LDAP' | translate}}</button>
     <span id="forTestingLDAP" class="spinner spinner-inline" [hidden]="hideLDAPTestingSpinner"></span>
-</div>
\ No newline at end of file
+</div>
diff --git a/src/portal/src/app/config/auth/config-auth.component.ts b/src/portal/src/app/config/auth/config-auth.component.ts
index 9ea4bc1f1..8a6e5e760 100644
--- a/src/portal/src/app/config/auth/config-auth.component.ts
+++ b/src/portal/src/app/config/auth/config-auth.component.ts
@@ -53,7 +53,6 @@ export class ConfigurationAuthComponent implements OnChanges {
 
     ngOnChanges(changes: SimpleChanges): void {
         if (changes && changes["currentConfig"]) {
-
             this.originalConfig = clone(this.currentConfig);
 
         }
@@ -153,9 +152,7 @@ export class ConfigurationAuthComponent implements OnChanges {
                 || prop === 'auth_mode'
                 || prop === 'project_creattion_restriction'
                 || prop === 'self_registration'
-                || prop === 'http_authproxy_endpoint'
-                || prop === 'http_authproxy_skip_cert_verify'
-                || prop === 'http_authproxy_always_onboard'
+                || prop.startsWith('http_')
             ) {
                 changes[prop] = allChanges[prop];
             }
diff --git a/src/portal/src/app/harbor-routing.module.ts b/src/portal/src/app/harbor-routing.module.ts
index 7413ca37f..8d8e10eb9 100644
--- a/src/portal/src/app/harbor-routing.module.ts
+++ b/src/portal/src/app/harbor-routing.module.ts
@@ -28,7 +28,7 @@ import { GcPageComponent } from './gc-page/gc-page.component';
 import { VulnerabilityPageComponent } from './vulnerability-page/vulnerability-page.component';
 
 import { UserComponent } from './user/user.component';
-import { SignInComponent } from './account/sign-in/sign-in.component';
+import { SignInComponent } from './sign-in/sign-in.component';
 import { ResetPasswordComponent } from './account/password-setting/reset-password/reset-password.component';
 import { GroupComponent } from './group/group.component';
 
@@ -69,17 +69,18 @@ const harborRoutes: Routes = [
     component: OidcOnboardComponent,
     canActivate: [OidcGuard, SignInGuard]
   },
+  {
+    path: 'harbor/sign-in',
+    component: SignInComponent,
+    canActivate: [ SignInGuard]
+  },
   {
     path: 'harbor',
     component: HarborShellComponent,
+    // canActivate: [AuthCheckGuard],
     canActivateChild: [AuthCheckGuard],
     children: [
-      { path: '', redirectTo: 'sign-in', pathMatch: 'full' },
-      {
-        path: 'sign-in',
-        component: SignInComponent,
-        canActivate: [SignInGuard]
-      },
+      { path: '', redirectTo: 'projects', pathMatch: 'full' },
       {
         path: 'projects',
         component: ProjectComponent
diff --git a/src/portal/src/app/shared/route/auth-user-activate.service.ts b/src/portal/src/app/shared/route/auth-user-activate.service.ts
index 6fc06aa24..35ea9a8ca 100644
--- a/src/portal/src/app/shared/route/auth-user-activate.service.ts
+++ b/src/portal/src/app/shared/route/auth-user-activate.service.ts
@@ -54,7 +54,7 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild {
     }
 
     this.searchTrigger.closeSearch(true);
-    return new Observable( observer => {
+    return new Observable(observer => {
       let queryParams = route.queryParams;
       if (queryParams) {
         if (queryParams[AdmiralQueryParamKey]) {
@@ -72,25 +72,27 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild {
       let user = this.authService.getCurrentUser();
       if (!user) {
         this.authService.retrieveUser()
-          .subscribe(() => observer.next(true)
-          , error => {
-            // If is guest, skip it
-            if (this.isGuest(route, state)) {
-              return observer.next(true);
-            }
-            // Session retrieving failed then redirect to sign-in
-            // no matter what status code is.
-            // Please pay attention that route 'HARBOR_ROOT' and 'EMBEDDED_SIGN_IN' support anonymous user
-            if (state.url !== CommonRoutes.HARBOR_ROOT && !state.url.startsWith(CommonRoutes.EMBEDDED_SIGN_IN)) {
-              let navigatorExtra: NavigationExtras = {
-                queryParams: { "redirect_url": state.url }
-              };
-              this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], navigatorExtra);
-              return observer.next(false);
-            } else {
-              return observer.next(true);
-            }
-          });
+          .subscribe(() => {
+            return observer.next(true);
+          }
+            , error => {
+              // If is guest, skip it
+              if (this.isGuest(route, state)) {
+                return observer.next(true);
+              }
+              // Session retrieving failed then redirect to sign-in
+              // no matter what status code is.
+              // Please pay attention that route 'HARBOR_ROOT' and 'EMBEDDED_SIGN_IN' support anonymous user
+              if (!state.url.startsWith(CommonRoutes.EMBEDDED_SIGN_IN)) {
+                let navigatorExtra: NavigationExtras = {
+                  queryParams: { "redirect_url": state.url }
+                };
+                this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], navigatorExtra);
+                return observer.next(false);
+              } else {
+                return observer.next(true);
+              }
+            });
       } else {
         return observer.next(true);
       }
diff --git a/src/portal/src/app/shared/session-user.ts b/src/portal/src/app/shared/session-user.ts
index 09e488ff8..cc762673f 100644
--- a/src/portal/src/app/shared/session-user.ts
+++ b/src/portal/src/app/shared/session-user.ts
@@ -21,4 +21,13 @@ export class SessionUser {
     role_id?: number;
     has_admin_role?: boolean;
     comment: string;
+    oidc_user_meta?: OidcUserMeta;
+}
+export class OidcUserMeta {
+    id: number;
+    user_id: number;
+    secret: string;
+    subiss: string;
+    creation_time: Date;
+    update_time: Date;
 }
diff --git a/src/portal/src/app/account/sign-in/sign-in.component.html b/src/portal/src/app/sign-in/sign-in.component.html
similarity index 91%
rename from src/portal/src/app/account/sign-in/sign-in.component.html
rename to src/portal/src/app/sign-in/sign-in.component.html
index 0e1a2de49..d13b1f396 100644
--- a/src/portal/src/app/account/sign-in/sign-in.component.html
+++ b/src/portal/src/app/sign-in/sign-in.component.html
@@ -1,3 +1,6 @@
+<clr-main-container><global-message [isAppLevel]="true"></global-message>
+<navigator></navigator>
+<search-result></search-result>
 <div class="login-wrapper"
     [ngStyle]="{'background-image': customLoginBgImg? 'url(static/images/' + customLoginBgImg + ')': ''}">
     <form #signInForm="ngForm" class="login">
@@ -37,8 +40,8 @@
             <div [class.visibility-hidden]="!isError" class="error active">
                 {{ 'SIGN_IN.INVALID_MSG' | translate }}
             </div>
-            <button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary"
-                (click)="signIn()" id="log_in">{{ 'BUTTON.LOG_IN' | translate }}</button>
+            <button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary" (click)="signIn()"
+                id="log_in">{{ 'BUTTON.LOG_IN' | translate }}</button>
             <a href="javascript:void(0)" class="signup" (click)="signUp()"
                 *ngIf="selfSignUp">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
         </div>
@@ -51,5 +54,6 @@
         <top-repo class="repo-container"></top-repo>
     </div>
 </div>
+</clr-main-container>
 <sign-up #signupDialog (userCreation)="handleUserCreation($event)"></sign-up>
 <forgot-password #forgotPwdDialog></forgot-password>
\ No newline at end of file
diff --git a/src/portal/src/app/account/sign-in/sign-in.component.scss b/src/portal/src/app/sign-in/sign-in.component.scss
similarity index 97%
rename from src/portal/src/app/account/sign-in/sign-in.component.scss
rename to src/portal/src/app/sign-in/sign-in.component.scss
index bf5800a69..21b4febd5 100644
--- a/src/portal/src/app/account/sign-in/sign-in.component.scss
+++ b/src/portal/src/app/sign-in/sign-in.component.scss
@@ -55,7 +55,6 @@
 
 .login-wrapper {
     flex-wrap: wrap;
-    margin-top:-20px;
     .login {
         background:transparent;
     }
diff --git a/src/portal/src/app/account/sign-in/sign-in.component.ts b/src/portal/src/app/sign-in/sign-in.component.ts
similarity index 93%
rename from src/portal/src/app/account/sign-in/sign-in.component.ts
rename to src/portal/src/app/sign-in/sign-in.component.ts
index bd69bd778..67b5b0e47 100644
--- a/src/portal/src/app/account/sign-in/sign-in.component.ts
+++ b/src/portal/src/app/sign-in/sign-in.component.ts
@@ -16,19 +16,19 @@ import { Router, ActivatedRoute } from '@angular/router';
 import { Input, ViewChild, AfterViewChecked } from '@angular/core';
 import { NgForm } from '@angular/forms';
 
-import { SessionService } from '../../shared/session.service';
-import { SignInCredential } from '../../shared/sign-in-credential';
+import { SessionService } from '../shared/session.service';
+import { SignInCredential } from '../shared/sign-in-credential';
 
-import { SignUpComponent } from '../sign-up/sign-up.component';
-import { CommonRoutes } from '../../shared/shared.const';
-import { ForgotPasswordComponent } from '../password-setting/forgot-password/forgot-password.component';
+import { SignUpComponent } from '../account/sign-up/sign-up.component';
+import { CommonRoutes } from '../shared/shared.const';
+import { ForgotPasswordComponent } from '../account/password-setting/forgot-password/forgot-password.component';
 
-import { AppConfigService } from '../../app-config.service';
-import { AppConfig } from '../../app-config';
-import { User } from '../../user/user';
+import { AppConfigService } from '../app-config.service';
+import { AppConfig } from '../app-config';
+import { User } from '../user/user';
 
 import { CookieService, CookieOptions } from 'ngx-cookie';
-import { SkinableConfig } from "../../skinable-config.service";
+import { SkinableConfig } from "../skinable-config.service";
 
 // Define status flags for signing in states
 export const signInStatusNormal = 0;
@@ -271,4 +271,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
     forgotPassword(): void {
         this.forgotPwdDialog.open();
     }
+
 }
+
+
diff --git a/src/portal/src/app/sign-in/sign-in.module.ts b/src/portal/src/app/sign-in/sign-in.module.ts
new file mode 100644
index 000000000..656de3da7
--- /dev/null
+++ b/src/portal/src/app/sign-in/sign-in.module.ts
@@ -0,0 +1,22 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { SignInComponent } from './sign-in.component';
+import { AccountModule } from '../account/account.module';
+import { BaseModule } from '../base/base.module';
+import { SharedModule } from '../shared/shared.module';
+import { RepositoryModule } from '../repository/repository.module';
+
+@NgModule({
+  declarations: [
+    SignInComponent,
+
+  ],
+  imports: [
+    CommonModule,
+    AccountModule,
+    SharedModule,
+    BaseModule,
+    RepositoryModule
+  ]
+})
+export class SignInModule { }
diff --git a/src/portal/src/app/account/sign-in/sign-in.service.ts b/src/portal/src/app/sign-in/sign-in.service.ts
similarity index 93%
rename from src/portal/src/app/account/sign-in/sign-in.service.ts
rename to src/portal/src/app/sign-in/sign-in.service.ts
index 75f3f5c26..7d81122a0 100644
--- a/src/portal/src/app/account/sign-in/sign-in.service.ts
+++ b/src/portal/src/app/sign-in/sign-in.service.ts
@@ -15,8 +15,8 @@ import { Injectable } from '@angular/core';
 import { Http, URLSearchParams } from '@angular/http';
 // import 'rxjs/add/operator/toPromise';
 
-import { SignInCredential } from '../../shared/sign-in-credential';
-import {HTTP_FORM_OPTIONS} from "../../shared/shared.utils";
+import { SignInCredential } from '../shared/sign-in-credential';
+import {HTTP_FORM_OPTIONS} from "../shared/shared.utils";
 import { map, catchError } from "rxjs/operators";
 import { Observable, throwError as observableThrowError } from "rxjs";
 const signInUrl = '/c/login';
diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json
index 8d6832615..0d79bda06 100644
--- a/src/portal/src/i18n/lang/en-us-lang.json
+++ b/src/portal/src/i18n/lang/en-us-lang.json
@@ -74,12 +74,12 @@
         "EMPTY": "Name is required",
         "NONEMPTY": "Can't be empty",
         "REPO_TOOLTIP": "Users can not do any operations to the images in this mode.",
-        "ENDPOINT_FORMAT": "Endpoint must start with HTTP or HTTPS.",
-        "OIDC_ENDPOIT_FORMAT": "Endpoint must start with HTTPS.",
+        "ENDPOINT_FORMAT": "Endpoint must start with HTTP:// or HTTPS://.",
+        "OIDC_ENDPOIT_FORMAT": "Endpoint must start with HTTPS://.",
         "OIDC_NAME": "The name of the OIDC provider.",
         "OIDC_ENDPOINT": "The URL of an OIDC-complaint server.",
         "OIDC_SCOPE": "The scope sent to OIDC server during authentication.  It has to contain “openid”, and “offline_access”.  If you are using google, please remove “offline_access” from this field.",
-        "OIDC_SKIPCERTVERIFY": "Check this box if your OIDC server is hosted via self-signed certificate."
+        "OIDC_VERIFYCERT": "Uncheck this box if your OIDC server is hosted via self-signed certificate."
     },
     "PLACEHOLDER": {
         "CURRENT_PWD": "Enter current password",
@@ -102,7 +102,11 @@
         "ADMIN_RENAME_BUTTON": "Change username",
         "ADMIN_RENAME_TIP": "Select the button in order to change the username to \"admin@harbor.local\". This operation can not be undone.",
         "RENAME_SUCCESS": "Rename success!",
-        "RENAME_CONFIRM_INFO": "Warning, changing the name to admin@harbor.local can not be undone."
+        "RENAME_CONFIRM_INFO": "Warning, changing the name to admin@harbor.local can not be undone.",
+        "CLI_PASSWORD": "CLI secret",
+        "CLI_PASSWORD_TIP": "You can use this cli secret as password when using docker/helm cli to access Harbor.",
+        "COPY_SUCCESS": "copy success",
+        "COPY_ERROR": "copy failed"
     },
     "CHANGE_PWD": {
         "TITLE": "Change Password",
@@ -705,7 +709,7 @@
             "FILTER": "LDAP Filter",
             "UID": "LDAP UID",
             "SCOPE": "LDAP Scope",
-            "VERIFY_CERT": "LDAP Verify Cert",
+            "VERIFY_CERT": "LDAP Verify Certificate",
             "LDAP_GROUP_BASE_DN": "LDAP Group Base DN",
             "LDAP_GROUP_BASE_DN_INFO": "The base DN from which to look up a group in LDAP/AD.",
             "LDAP_GROUP_FILTER": "LDAP Group Filter",
@@ -728,16 +732,17 @@
         },
         "HTTP_AUTH": {
             "ENDPOINT": "Server Endpoint",
+            "TOKEN_REVIEW": "Token Review Endpoint",
             "ALWAYS_ONBOARD": "Always Onboard",
-            "VERIFY_CERT": "Authentication Verify Cert"
+            "VERIFY_CERT": "Verify Certificate"
         },
         "OIDC": {
-            "OIDC_PROVIDER": "OIDC Provider",
+            "OIDC_PROVIDER": "OIDC Provider Name",
             "ENDPOINT": "OIDC Endpoint",
             "CLIENT_ID": "OIDC Client ID",
             "CLIENTSECRET": "OIDC Client Secret",
             "SCOPE": "OIDC Scope",
-            "OIDCSKIPCERTVERIFY": "OIDC Skip Verifying Certificate",
+            "OIDC_VERIFYCERT": "Verify Certificate",
             "OIDC_SETNAME": "Set OIDC Username",
             "OIDC_SETNAMECONTENT": "You must create a Harbor username the first time when authenticating via a third party(OIDC).This will be used within Harbor to be associated with projects, roles, etc.",
             "OIDC_USERNAME": "Username"
diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json
index 674033f3e..5acfcc746 100644
--- a/src/portal/src/i18n/lang/es-es-lang.json
+++ b/src/portal/src/i18n/lang/es-es-lang.json
@@ -74,12 +74,12 @@
         "EMPTY": "Name is required",
         "NONEMPTY": "Can't be empty",
         "REPO_TOOLTIP": "Users can not do any operations to the images in this mode.",
-        "ENDPOINT_FORMAT": "Endpoint must start with HTTP or HTTPS.",
-        "OIDC_ENDPOIT_FORMAT": "Endpoint must start with HTTPS.",
+        "ENDPOINT_FORMAT": "Endpoint must start with HTTP:// or HTTPS://.",
+        "OIDC_ENDPOIT_FORMAT": "Endpoint must start with HTTPS://.",
         "OIDC_NAME": "El nombre de la OIDC proveedor.",
         "OIDC_ENDPOINT": "La dirección URL de un servidor OIDC denuncia.",
         "OIDC_SCOPE": "El ámbito de aplicación enviada a OIDC Server durante la autenticación.Tiene que contener 'Openid', y 'offline_access'.Si usted esta usando Google, por favor quitar 'offline_access' de este campo",
-        "OIDC_SKIPCERTVERIFY": "Marque esta casilla si tu OIDC servidor está alojado a través de certificado autofirmado."
+        "OIDC_VERIFYCERT": "Desmarque esta casilla si tu OIDC servidor está alojado a través de certificado autofirmado."
     },
     "PLACEHOLDER": {
         "CURRENT_PWD": "Introduzca la contraseña actual",
@@ -102,7 +102,11 @@
         "ADMIN_RENAME_BUTTON": "Change username",
         "ADMIN_RENAME_TIP": "Select the button in order to change the username to \"admin@harbor.local\". This operation can not be undone.",
         "RENAME_SUCCESS": "Rename success!",
-        "RENAME_CONFIRM_INFO": "Warning, changing the name to admin@harbor.local can not be undone."
+        "RENAME_CONFIRM_INFO": "Warning, changing the name to admin@harbor.local can not be undone.",
+        "CLI_PASSWORD": "CLI secreto",
+        "CLI_PASSWORD_TIP": "Puede utilizar este generador CLI secreto como utilizando Docker / Helm CLI para acceder a puerto.",
+        "COPY_SUCCESS": "Copiar el éxito",
+        "COPY_ERROR": "Copia no"
     },
     "CHANGE_PWD": {
         "TITLE": "Cambiar contraseña",
@@ -727,6 +731,7 @@
         },
         "HTTP_AUTH": {
             "ENDPOINT": "Server Endpoint",
+            "TOKEN_REVIEW": "Review Endpoint De Token",
             "ALWAYS_ONBOARD": "Always Onboard",
             "VERIFY_CERT": "Authentication Verify Cert"
         },
@@ -736,7 +741,7 @@
             "CLIENT_ID": "ID de cliente OIDC",
             "CLIENTSECRET": "OIDC Client Secret",
             "SCOPE": "OIDC Ámbito",
-            "OIDCSKIPCERTVERIFY": "OIDC Skip Verificar certificado",
+            "OIDC_VERIFYCERT": "Verificar certificado",
             "OIDC_SETNAME": "Set OIDC nombre de usuario",
             "OIDC_SETNAMECONTENT": "Usted debe crear un Harbor nombre de usuario la primera vez cuando la autenticación a través de un tercero (OIDC). Esta será usada en Harbor para ser asociados con proyectos, funciones, etc.",
             "OIDC_USERNAME": "Usuario"
diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json
index e8da0d3e1..333e1d822 100644
--- a/src/portal/src/i18n/lang/fr-fr-lang.json
+++ b/src/portal/src/i18n/lang/fr-fr-lang.json
@@ -61,12 +61,12 @@
         "USER_EXISTING": "Le nom d'utilisateur est déjà utilisé.",
         "NONEMPTY": "Can't be empty",
         "REPO_TOOLTIP": "Users can not do any operations to the images in this mode.",
-        "ENDPOINT_FORMAT": "Endpoint must start with HTTP or HTTPS.",
-        "OIDC_ENDPOIT_FORMAT": "Endpoint must start with HTTPS.",
+        "ENDPOINT_FORMAT": "Endpoint must start with HTTP:// or HTTPS://.",
+        "OIDC_ENDPOIT_FORMAT": "Endpoint must start with HTTPS://.",
         "OIDC_NAME": "le nom du fournisseur de oidc.",
         "OIDC_ENDPOINT": "l'url d'un serveur oidc plainte.",
         "OIDC_SCOPE": "le champ envoyés au serveur au cours oidc l'authentification.il doit contenir 'openid', et 'offline_access'.si vous utilisez google, veuillez supprimer 'offline_access' dans ce domaine",
-        "OIDC_SKIPCERTVERIFY": "cocher cette case si votre oidc serveur est accueilli par auto - certificat signé."
+        "OIDC_VERIFYCERT": "décocher cette case si votre oidc serveur est accueilli par auto - certificat signé."
     },
     "PLACEHOLDER": {
         "CURRENT_PWD": "Entrez le mot de passe actuel",
@@ -89,7 +89,11 @@
         "ADMIN_RENAME_BUTTON": "Change username",
         "ADMIN_RENAME_TIP": "Select the button in order to change the username to \"admin@harbor.local\". This operation can not be undone.",
         "RENAME_SUCCESS": "Rename success!",
-        "RENAME_CONFIRM_INFO": "Warning, changing the name to admin@harbor.local can not be undone."
+        "RENAME_CONFIRM_INFO": "Warning, changing the name to admin@harbor.local can not be undone.",
+        "CLI_PASSWORD": "CLI secret",
+        "CLI_PASSWORD_TIP": "vous pouvez utiliser ce cli secret comme mot de passe quand utiliser docker / barre l'accès à harbor.",
+        "COPY_SUCCESS": "copie de succès",
+        "COPY_ERROR": "copie a échoué"
     },
     "CHANGE_PWD": {
         "TITLE": "Modifier le mot de passe",
@@ -692,7 +696,8 @@
         },
         "HTTP_AUTH": {
             "ENDPOINT": "serveur paramètre",
-            "ALWAYS_ONBOARD": "Always Onboard",
+            "TOKEN_REVIEW": "examen symbolique paramètre",
+            "ALWAYS_ONBOARD": "always onboard",
             "VERIFY_CERT": "authentification vérifier cert"
         },
         "OIDC": {
@@ -701,7 +706,7 @@
             "CLIENT_ID": "no d'identification du client OIDC",
             "CLIENTSECRET": "OIDC Client Secret",
             "SCOPE": "OIDC Scope",
-            "OIDCSKIPCERTVERIFY": "Certificat OIDC skip vérifier",
+            "OIDC_VERIFYCERT": "Certificat vérifier",
             "OIDC_SETNAME": "Ensemble OIDC nom d'utilisateur",
             "OIDC_SETNAMECONTENT": "vous devez créer un Harbor identifiant la première fois lors de la vérification par une tierce partie (oidc). il sera utilisé au sein de port à être associés aux projets, des rôles, etc.",
             "OIDC_USERNAME": "d'utilisateur"
diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json
index f7ff18433..6f4c18141 100644
--- a/src/portal/src/i18n/lang/pt-br-lang.json
+++ b/src/portal/src/i18n/lang/pt-br-lang.json
@@ -72,12 +72,12 @@
         "USER_EXISTING": "Nome de usuário já está em uso.",
         "RULE_USER_EXISTING": "Nome já em uso.",
         "EMPTY": "Nome é obrigatório",
-        "ENDPOINT_FORMAT": "Avaliação deve começar por HTTP Ou HTTPS.",
-        "OIDC_ENDPOIT_FORMAT": "Avaliação deve começar por HTTPS.",
+        "ENDPOINT_FORMAT": "Avaliação deve começar por HTTP:// Ou HTTPS://.",
+        "OIDC_ENDPOIT_FORMAT": "Avaliação deve começar por HTTPS://.",
         "OIDC_NAME": "O Nome do prestador de oidc.",
         "OIDC_ENDPOINT": "A URL de um servidor oidc denúncia.",
         "OIDC_SCOPE": "O âmbito de aplicação enviada Ao servidor oidc Durante a autenticação.TEM que conter 'openid' e 'offline_access'.Se você está usando o Google, por favor remova 'offline_access' desse Campo.",
-        "OIDC_SKIPCERTVERIFY": "Assinale esta opção se o SEU servidor está hospedado oidc via self - signed certificate."
+        "OIDC_VERIFYCERT": "Desmarque esta opção se o SEU servidor está hospedado oidc via self - signed certificate."
     },
     "PLACEHOLDER": {
         "CURRENT_PWD": "Insira a senha atual",
@@ -100,7 +100,11 @@
         "ADMIN_RENAME_BUTTON": "Alterar nome de usuário",
         "ADMIN_RENAME_TIP": "Selecione o botão para alterar o nome de usuário para \"admin@harbor.local\". Essa operação não pode ser desfeita.",
         "RENAME_SUCCESS": "Renomeado com sucesso!",
-        "RENAME_CONFIRM_INFO": "Atenção, alterar o nome para admin@harbor.local não pode ser desfeito."
+        "RENAME_CONFIRM_INFO": "Atenção, alterar o nome para admin@harbor.local não pode ser desfeito.",
+        "CLI_PASSWORD": "Segredo CLI",
+        "CLI_PASSWORD_TIP": "Você Pode USAR este Segredo de clitóris Como senha Ao USAR clitóris de estivador /leme para acessar Harbor.",
+        "COPY_SUCCESS": "SUCESSO de cópia",
+        "COPY_ERROR": "Cópia falhou"
     },
     "CHANGE_PWD": {
         "TITLE": "Alterar Senha",
@@ -720,7 +724,8 @@
             "VERIFY_CERT": "Verificar certificado UAA"
         },
         "HTTP_AUTH": {
-            "ENDPOINT": "server endpoint",
+            "ENDPOINT": "Server endpoint",
+            "TOKEN_REVIEW": "Ponto final do Token Review",
             "ALWAYS_ONBOARD": "Sempre Onboard",
             "VERIFY_CERT": "Verificar certificado de Authentication"
         },
@@ -730,7 +735,7 @@
             "CLIENT_ID": "ID de cliente OIDC",
             "CLIENTSECRET": "OIDC Client Secret",
             "SCOPE": "Escopo OIDC",
-            "OIDCSKIPCERTVERIFY": "OIDC Skip Verificar Certificado",
+            "OIDC_VERIFYCERT": "Verificar Certificado",
             "OIDC_SETNAME": "Definir o Utilizador OIDC",
             "OIDC_SETNAMECONTENT": "Você deve Criar um Nome de usuário do Porto a primeira vez que autenticar através de um terceiro (OIDC). Isto será usado Dentro de Harbor para ser associado a projetos, papéis, etc.",
             "OIDC_USERNAME": "Utilizador"
diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json
index 567d50516..1cb00ffda 100644
--- a/src/portal/src/i18n/lang/zh-cn-lang.json
+++ b/src/portal/src/i18n/lang/zh-cn-lang.json
@@ -73,12 +73,12 @@
         "RULE_USER_EXISTING": "名称已经存在。",
         "EMPTY": "名称为必填项",
         "NONEMPTY": "不能为空",
-        "ENDPOINT_FORMAT": "Endpoint必须以http或https开头。",
-        "OIDC_ENDPOIT_FORMAT": "Endpoint必须以https开头。",
+        "ENDPOINT_FORMAT": "Endpoint必须以http://或https://开头。",
+        "OIDC_ENDPOIT_FORMAT": "Endpoint必须以https://开头。",
         "OIDC_NAME": "OIDC提供商的名称.",
         "OIDC_ENDPOINT": "OIDC服务器的地址.",
         "OIDC_SCOPE": "在身份验证期间发送到OIDC服务器的scope。它必须包含“openid”和“offline_access”。如果您使用Google,请从此字段中删除“脱机访问”。",
-        "OIDC_SKIPCERTVERIFY": "如果您的OIDC服务器是通过自签名证书托管的,请选中此框。"
+        "OIDC_VERIFYCERT": "如果您的OIDC服务器是通过自签名证书托管的,请取消选中此框。"
     },
     "PLACEHOLDER": {
         "CURRENT_PWD": "输入当前密码",
@@ -101,7 +101,11 @@
         "ADMIN_RENAME_TIP": "单击将用户名改为 \"admin@harbor.local\", 注意这个操作是无法撤销的",
         "RENAME_SUCCESS": "用户名更改成功!",
         "ADMIN_RENAME_BUTTON": "更改用户名",
-        "RENAME_CONFIRM_INFO": "更改用户名为admin@harbor.local是无法撤销的, 你确定更改吗?"
+        "RENAME_CONFIRM_INFO": "更改用户名为admin@harbor.local是无法撤销的, 你确定更改吗?",
+        "CLI_PASSWORD": "CLI密码",
+        "CLI_PASSWORD_TIP": "使用docker/helm cli访问Harbor时,可以使用此cli密码作为密码。",
+        "COPY_SUCCESS": "复制成功",
+        "COPY_ERROR": "复制失败"
     },
     "CHANGE_PWD": {
         "TITLE": "修改密码",
@@ -726,6 +730,7 @@
         },
         "HTTP_AUTH": {
             "ENDPOINT": "Server Endpoint",
+            "TOKEN_REVIEW": "Token Review Endpoint",
             "ALWAYS_ONBOARD": "Always Onboard",
             "VERIFY_CERT": "Authentication验证证书"
         },
@@ -735,7 +740,7 @@
             "CLIENT_ID": "OIDC 客户端标识",
             "CLIENTSECRET": "OIDC 客户端密码",
             "SCOPE": "OIDC Scope",
-            "OIDCSKIPCERTVERIFY": "OIDC 验证证书",
+            "OIDC_VERIFYCERT": "验证证书",
             "OIDC_SETNAME": "设置OIDC用户名",
             "OIDC_SETNAMECONTENT": "在通过第三方(OIDC)进行身份验证时,您必须第一次创建一个Harbor用户名。这将在端口中用于与项目、角色等关联。",
             "OIDC_USERNAME": "用户名"
diff --git a/tests/apitests/python/library/project.py b/tests/apitests/python/library/project.py
index 970cb9e7a..c563103c7 100644
--- a/tests/apitests/python/library/project.py
+++ b/tests/apitests/python/library/project.py
@@ -15,6 +15,12 @@ def is_member_exist_in_project(members, member_user_name, expected_member_role_i
                 return True
     return result
 
+def get_member_id_by_name(members, member_user_name):
+    for member in members:
+        if member.entity_name == member_user_name:
+            return member.id
+    return None
+
 class Project(base.Base):
     def create_project(self, name=None, metadata=None, expect_status_code = 201, expect_response_body = None, **kwargs):
         if name is None:
@@ -131,6 +137,14 @@ class Project(base.Base):
         base._assert_status_code(200, status_code)
         return data
 
+    def get_project_member_id(self, project_id, member_user_name, **kwargs):
+        members = self.get_project_members(project_id, **kwargs)
+        result = get_member_id_by_name(list(members), member_user_name)
+        if result == None:
+            raise Exception(r"Failed to get member id of member {} in project {}.".format(member_user_name, project_id))
+        else:
+            return result
+
     def check_project_member_not_exist(self, project_id, member_user_name, **kwargs):
         members = self.get_project_members(project_id, **kwargs)
         result = is_member_exist_in_project(list(members), member_user_name)
diff --git a/tests/apitests/python/library/repository.py b/tests/apitests/python/library/repository.py
index ccb2d4439..19c1ffc76 100644
--- a/tests/apitests/python/library/repository.py
+++ b/tests/apitests/python/library/repository.py
@@ -4,6 +4,7 @@ import time
 import base
 import swagger_client
 from docker_api import DockerAPI
+from swagger_client.rest import ApiException
 
 def pull_harbor_image(registry, username, password, image, tag, expected_error_message = None):
     _docker_api = DockerAPI()
@@ -94,7 +95,7 @@ class Repository(base.Base):
             time.sleep(5)
             timeout_count = timeout_count - 1
             if (timeout_count == 0):
-                break            
+                break
             _tag = self.get_tag(repo_name, tag, **kwargs)
             if _tag.name == tag and _tag.scan_overview !=None:
                 if _tag.scan_overview.scan_status == expected_scan_status:
@@ -118,4 +119,20 @@ class Repository(base.Base):
             if each_sign.tag == tag and len(each_sign.hashes["sha256"]) == 44:
                 print "sha256:", len(each_sign.hashes["sha256"])
                 return
-        raise Exception(r"Signature of {}:{} is not exist!".format(repo_name, tag))
\ No newline at end of file
+        raise Exception(r"Signature of {}:{} is not exist!".format(repo_name, tag))
+
+    def retag_image(self, repo_name, tag, src_image, override=True, expect_status_code = 200, expect_response_body = None, **kwargs):
+        client = self._get_client(**kwargs)
+        request = swagger_client.RetagReq(tag=tag, src_image=src_image, override=override)
+
+        try:
+            data, status_code, _ = client.repositories_repo_name_tags_post_with_http_info(repo_name, request)
+        except ApiException as e:
+            base._assert_status_code(expect_status_code, e.status)
+            if expect_response_body is not None:
+                base._assert_status_body(expect_response_body, e.body)
+            return
+
+        base._assert_status_code(expect_status_code, status_code)
+        base._assert_status_code(200, status_code)
+        return data
\ No newline at end of file
diff --git a/tests/apitests/python/test_edit_project_creation.py b/tests/apitests/python/test_edit_project_creation.py
index 53b4056e0..7243a264b 100644
--- a/tests/apitests/python/test_edit_project_creation.py
+++ b/tests/apitests/python/test_edit_project_creation.py
@@ -59,7 +59,7 @@ class TestProjects(unittest.TestCase):
 
         #3. Create a new project(PA) by user(UA), and fail to create a new project;
         self.project.create_project(metadata = {"public": "false"}, expect_status_code = 403,
-            expect_response_body = "Only system admin can create project", **TestProjects.USER_edit_project_creation_CLIENT)
+            expect_response_body = "{\"code\":403,\"message\":\"Only system admin can create project\"}", **TestProjects.USER_edit_project_creation_CLIENT)
 
         #4. Set project creation to "everyone";
         self.conf.set_configurations_of_project_creation_restriction("everyone", **ADMIN_CLIENT)
diff --git a/tests/apitests/python/test_retag.py b/tests/apitests/python/test_retag.py
new file mode 100644
index 000000000..356766295
--- /dev/null
+++ b/tests/apitests/python/test_retag.py
@@ -0,0 +1,127 @@
+from __future__ import absolute_import
+
+
+import unittest
+
+from library.base import _assert_status_code
+from testutils import ADMIN_CLIENT
+from testutils import harbor_server
+
+from testutils import TEARDOWN
+from library.project import Project
+from library.user import User
+from library.repository import Repository
+from library.repository import push_image_to_project
+from library.repository import pull_harbor_image
+
+class TestProjects(unittest.TestCase):
+    @classmethod
+    def setUpClass(self):
+        project = Project()
+        self.project= project
+
+        user = User()
+        self.user= user
+
+        repo = Repository()
+        self.repo= repo
+
+    @classmethod
+    def tearDownClass(self):
+        print "Case completed"
+
+    @unittest.skipIf(TEARDOWN == False, "Test data won't be erased.")
+    def test_ClearData(self):
+        #1. Delete repository(RA);
+        self.repo.delete_repoitory(TestProjects.src_repo_name, **TestProjects.USER_RETAG_CLIENT)
+
+        #2. Delete repository by retag;
+        self.repo.delete_repoitory(TestProjects.dst_repo_name, **TestProjects.USER_RETAG_CLIENT)
+
+        #3. Delete project(PA);
+        self.project.delete_project(TestProjects.project_src_repo_id, **TestProjects.USER_RETAG_CLIENT)
+        self.project.delete_project(TestProjects.project_dst_repo_id, **TestProjects.USER_RETAG_CLIENT)
+
+        #4. Delete user(UA).
+        self.user.delete_user(TestProjects.user_retag_id, **ADMIN_CLIENT)
+
+    def testRetag(self):
+        """
+        Test case:
+            Retag Image
+        Test step and expected result:
+            1. Create a new user(UA);
+            2. Create a new project(PA) by user(UA);
+            3. Create a new project(PB) by user(UA);
+            4. Update role of user-retag as guest member of project(PB);
+            5. Create a new repository(RA) in project(PA) by user(UA);
+            6. Get repository in project(PA), there should be one repository which was created by user(UA);
+            7. Get repository(RA)'s image tag detail information;
+            8. Retag image in project(PA) to project(PB), it should be forbidden;
+            9. Update role of user-retag as admin member of project(PB);
+            10. Retag image in project(PA) to project(PB), it should be successful;
+            11. Get repository(RB)'s image tag detail information;
+            12. Read digest of retaged image, it must be the same with the image in repository(RA);
+            13. Pull image from project(PB) by user_retag, it must be successful;
+        Tear down:
+            1. Delete repository(RA);
+            2. Delete repository by retag;
+            3. Delete project(PA);
+            4. Delete user(UA).
+        """
+        url = ADMIN_CLIENT["endpoint"]
+        user_retag_password = "Aa123456"
+        pull_tag_name = "latest"
+        dst_repo_sub_name = "repo"
+        dst_tag_name = "test_tag"
+
+        #1. Create a new user(UA);
+        TestProjects.user_retag_id, user_retag_name = self.user.create_user(user_password = user_retag_password, **ADMIN_CLIENT)
+
+        TestProjects.USER_RETAG_CLIENT=dict(endpoint = url, username = user_retag_name, password = user_retag_password)
+
+        #2. Create a new project(PA) by user(UA);
+        TestProjects.project_src_repo_id, project_src_repo_name = self.project.create_project(metadata = {"public": "false"}, **TestProjects.USER_RETAG_CLIENT)
+
+        #3. Create a new project(PB) by user(UA);
+        TestProjects.project_dst_repo_id, project_dst_repo_name = self.project.create_project(metadata = {"public": "false"}, **TestProjects.USER_RETAG_CLIENT)
+
+        retag_member_id = self.project.get_project_member_id(TestProjects.project_dst_repo_id, user_retag_name, **TestProjects.USER_RETAG_CLIENT)
+
+        #4. Update role of user-retag as guest member of project(PB);
+        self.project.update_project_member_role(TestProjects.project_dst_repo_id, retag_member_id, 3, **ADMIN_CLIENT)
+
+        #5. Create a new repository(RA) in project(PA) by user(UA);
+        TestProjects.src_repo_name, tag_name = push_image_to_project(project_src_repo_name, harbor_server, 'admin', 'Harbor12345', "hello-world", pull_tag_name)
+
+        #6. Get repository in project(PA), there should be one repository which was created by user(UA);
+        src_repo_data = self.repo.get_repository(TestProjects.project_src_repo_id, **TestProjects.USER_RETAG_CLIENT)
+        _assert_status_code(TestProjects.src_repo_name, src_repo_data[0].name)
+
+        #7. Get repository(RA)'s image tag detail information;
+        src_tag_data = self.repo.get_tag(TestProjects.src_repo_name, tag_name, **TestProjects.USER_RETAG_CLIENT)
+
+        TestProjects.dst_repo_name = project_dst_repo_name+"/"+ dst_repo_sub_name
+
+        #8. Retag image in project(PA) to project(PB), it should be forbidden;
+        self.repo.retag_image(TestProjects.dst_repo_name, dst_tag_name, TestProjects.src_repo_name+":"+src_tag_data.digest, expect_status_code=403, **TestProjects.USER_RETAG_CLIENT)
+
+        #9. Update role of user-retag as admin member of project(PB);
+        self.project.update_project_member_role(TestProjects.project_dst_repo_id, retag_member_id, 1, **ADMIN_CLIENT)
+
+        #10. Retag image in project(PA) to project(PB), it should be successful;
+        self.repo.retag_image(TestProjects.dst_repo_name, dst_tag_name, TestProjects.src_repo_name+":"+src_tag_data.digest, **TestProjects.USER_RETAG_CLIENT)
+
+        #11. Get repository(RB)'s image tag detail information;
+        dst_tag_data = self.repo.get_tag(TestProjects.dst_repo_name, dst_tag_name, **TestProjects.USER_RETAG_CLIENT)
+
+        #12. Read digest of retaged image, it must be the same with the image in repository(RA);
+        self.assertEqual(src_tag_data.digest, dst_tag_data.digest)
+
+        #13. Pull image from project(PB) by user_retag, it must be successful;"
+        pull_harbor_image(harbor_server, user_retag_name, user_retag_password, TestProjects.dst_repo_name, dst_tag_name)
+
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/tests/resources/Harbor-Pages/HomePage_Elements.robot b/tests/resources/Harbor-Pages/HomePage_Elements.robot
index 96a13ffe3..d1ecc1208 100644
--- a/tests/resources/Harbor-Pages/HomePage_Elements.robot
+++ b/tests/resources/Harbor-Pages/HomePage_Elements.robot
@@ -16,7 +16,7 @@
 Documentation  This resource provides any keywords related to the Harbor private registry appliance
 
 *** Variables ***
-${sign_up_for_an_account_xpath}  /html/body/harbor-app/harbor-shell/clr-main-container/div/div/sign-in/div/form/div[1]/a
+${sign_up_for_an_account_xpath}  /html/body/harbor-app/sign-in/clr-main-container/div/form/div[1]/a
 ${sign_up_button_xpath}  //a[@class='signup']
 ${username_xpath}  //*[@id='username']
 ${email_xpath}  //*[@id='email']
diff --git a/tests/resources/Harbor-Pages/Public_Elements.robot b/tests/resources/Harbor-Pages/Public_Elements.robot
new file mode 100644
index 000000000..6b8f3836c
--- /dev/null
+++ b/tests/resources/Harbor-Pages/Public_Elements.robot
@@ -0,0 +1,21 @@
+# Copyright Project Harbor Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#	http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+*** Settings ***
+Documentation  This resource provides any keywords related to public
+
+*** Variables ***
+${delete_btn}  //clr-modal//button[contains(.,'DELETE')]
+${delete_btn_2}  //button[contains(.,'Delete')]
+
diff --git a/tests/resources/Harbor-Pages/ToolKit.robot b/tests/resources/Harbor-Pages/ToolKit.robot
index bea0601fe..7b1954c64 100644
--- a/tests/resources/Harbor-Pages/ToolKit.robot
+++ b/tests/resources/Harbor-Pages/ToolKit.robot
@@ -67,34 +67,27 @@ Multi-delete Object
 Multi-delete User
     [Arguments]    @{obj}
     :For  ${obj}  in  @{obj}
-    \    Click Element  //clr-dg-row[contains(.,'${obj}')]//label
-    Sleep  1
-    Click Element  ${member_action_xpath}
-    Sleep  1
-    Click Element  //clr-dropdown/clr-dropdown-menu/button[2]
-    Sleep  2
-    Click Element  //clr-modal//button[contains(.,'DELETE')]
-    Sleep  3
+    \    Retry Element Click  //clr-dg-row[contains(.,'${obj}')]//label
+    Retry Element Click  ${member_action_xpath}
+    Retry Element Click  //clr-dropdown/clr-dropdown-menu/button[2]
+    Retry Double Keywords When Error  Retry Element Click  ${delete_btn}  Retry Wait Until Page Not Contains Element  ${delete_btn}
+
 
 Multi-delete Member
     [Arguments]    @{obj}
     :For  ${obj}  in  @{obj}
-    \    Click Element  //clr-dg-row[contains(.,'${obj}')]//label
-    Sleep  1
-    Click Element  ${member_action_xpath}
-    Sleep  1
-    Click Element  ${delete_action_xpath}
-    Sleep  2
-    Click Element  //clr-modal//button[contains(.,'DELETE')]
-    Sleep  3
+    \    Retry Element Click  //clr-dg-row[contains(.,'${obj}')]//label
+    Retry Element Click  ${member_action_xpath}
+    Retry Element Click  ${delete_action_xpath}
+    Retry Double Keywords When Error  Retry Element Click  ${delete_btn}  Retry Wait Until Page Not Contains Element  ${delete_btn}
+
 
 Multi-delete Object Without Confirmation
     [Arguments]    @{obj}
     :For  ${obj}  in  @{obj}
-    \    Click Element  //clr-dg-row[contains(.,'${obj}')]//label
-    Sleep  1
-    Click Element  //button[contains(.,'Delete')]
-    Sleep  3
+    \    Retry Element Click  //clr-dg-row[contains(.,'${obj}')]//label
+    Retry Double Keywords When Error  Retry Element Click  ${delete_btn_2}  Retry Wait Until Page Not Contains Element  ${delete_btn_2}
+
 
 Select All On Current Page Object
-    Click Element  //div[@class='datagrid-head']//label
+    Retry Element Click  //div[@class='datagrid-head']//label
diff --git a/tests/resources/Util.robot b/tests/resources/Util.robot
index d844c4a94..5b2971676 100644
--- a/tests/resources/Util.robot
+++ b/tests/resources/Util.robot
@@ -28,6 +28,7 @@ Resource  VCH-Util.robot
 Resource  Drone-Util.robot
 Resource  Github-Util.robot
 Resource  Harbor-Util.robot
+Resource  Harbor-Pages/Public_Elements.robot
 Resource  Harbor-Pages/HomePage.robot
 Resource  Harbor-Pages/HomePage_Elements.robot
 Resource  Harbor-Pages/Project.robot
diff --git a/tests/robot-cases/Group0-BAT/API_DB.robot b/tests/robot-cases/Group0-BAT/API_DB.robot
index 7b4fd8a2d..baf555c1a 100644
--- a/tests/robot-cases/Group0-BAT/API_DB.robot
+++ b/tests/robot-cases/Group0-BAT/API_DB.robot
@@ -42,4 +42,6 @@ Test Case - Scan All Images
 Test Case - List Helm Charts
     Harbor API Test  ./tests/apitests/python/test_list_helm_charts.py
 Test Case - Assign Sys Admin
-    Harbor API Test  ./tests/apitests/python/test_assign_sys_admin.py
\ No newline at end of file
+    Harbor API Test  ./tests/apitests/python/test_assign_sys_admin.py
+Test Case - Retag Image
+    Harbor API Test  ./tests/apitests/python/test_retag.py
\ No newline at end of file