Repository: avenga/couper
Pull request #438 (2022-02-10 14:31:44)

Fix jwt error kinds
All invalid tokens cause error with "jwt_token_invalid".
Missing tokens in `Authorization: Bearer` cause error with "jwt_token_missing".

---
<details>
    <summary>Reviewer checklist</summary>
    <ul>
        <li>Read PR description: a summary about the changes is required</li>
        <li>Changelog updated</li>
        <li>Documentation: docs/{Reference, Cli, ...}, Docker and cli help/usage</li>
        <li>Pulled branch, manually tested</li>
        <li>Verified requirements are met</li>
        <li>Reviewed the code</li>
        <li>Reviewed the related tests</li>
    </ul>
</details>



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d49ff04b..93f6cc58 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ Unreleased changes are available as `avenga/couper:edge` container.
 * **Fixed**
   * improved protection against sniffing using unauthorized requests with non-standard method to non-existant endpoints in protected API ([#441](https://github.com/avenga/couper/pull/441))
   * Couper handles OS-Signal `INT` in all cases in combination with the `-watch` argument ([#456](https://github.com/avenga/couper/pull/456))
+  * some [error types](./docs/ERRORS.md#access-control-error-types) related to [JWT](./docs/REFERENCE.md#jwt-block) ([#438](https://github.com/avenga/couper/pull/438))
 
 ---
 
diff --git a/accesscontrol/jwt.go b/accesscontrol/jwt.go
index 2cdd2932..d704891c 100644
--- a/accesscontrol/jwt.go
+++ b/accesscontrol/jwt.go
@@ -199,7 +199,7 @@ func (j *JWT) Validate(req *http.Request) error {
 		if strings.ToLower(j.source.Name) == "authorization" {
 			if tokenValue = req.Header.Get(j.source.Name); tokenValue != "" {
 				if tokenValue, err = getBearer(tokenValue); err != nil {
-					return err
+					return errors.JwtTokenMissing.With(err)
 				}
 			}
 		} else {
@@ -219,16 +219,7 @@ func (j *JWT) Validate(req *http.Request) error {
 		return errors.JwtTokenMissing.Message("token required")
 	}
 
-	claims := make(map[string]interface{})
-	if j.claims != nil {
-		val, verr := eval.Value(eval.ContextFromRequest(req).HCLContext(), j.claims)
-		if verr != nil {
-			return verr
-		}
-		claims = seetie.ValueToMap(val)
-	}
-
-	parser, err := newParser(j.algorithms, claims)
+	claims, iss, aud, err := j.getConfiguredClaims(req)
 	if err != nil {
 		return err
 	}
@@ -238,6 +229,8 @@ func (j *JWT) Validate(req *http.Request) error {
 		// load JWKS if needed and associate with request uid
 		j.jwks.Data(ctx.Value(request.UID).(string))
 	}
+
+	parser := newParser(j.algorithms, iss, aud)
 	token, err := parser.Parse(tokenValue, j.getValidationKey)
 	if err != nil {
 		switch err := err.(type) {
@@ -245,15 +238,15 @@ func (j *JWT) Validate(req *http.Request) error {
 			return errors.JwtTokenExpired.With(err)
 		case *jwt.UnverfiableTokenError:
 			if unwrappedError := err.ErrorWrapper.Unwrap(); unwrappedError != nil {
-				return unwrappedError
+				return errors.JwtTokenInvalid.With(unwrappedError)
 			}
 		}
-		return err
+		return errors.JwtTokenInvalid.With(err)
 	}
 
 	tokenClaims, err := j.validateClaims(token, claims)
 	if err != nil {
-		return err
+		return errors.JwtTokenInvalid.With(err)
 	}
 
 	acMap, ok := ctx.Value(request.AccessControls).(map[string]interface{})
@@ -315,6 +308,37 @@ func (j *JWT) getValidationKey(token *jwt.Token) (interface{}, error) {
 	}
 }
 
+// getConfiguredClaims evaluates the expected claim values from the configuration, and especially iss and aud
+func (j *JWT) getConfiguredClaims(req *http.Request) (map[string]interface{}, string, string, error) {
+	claims := make(map[string]interface{})
+	var iss, aud string
+	if j.claims != nil {
+		val, verr := eval.Value(eval.ContextFromRequest(req).HCLContext(), j.claims)
+		if verr != nil {
+			return nil, "", "", verr
+		}
+		claims = seetie.ValueToMap(val)
+
+		var ok bool
+		if issVal, exists := claims["iss"]; exists {
+			iss, ok = issVal.(string)
+			if !ok {
+				return nil, "", "", errors.Configuration.Message("invalid value type, string expected (claims / iss)")
+			}
+		}
+
+		if audVal, exists := claims["aud"]; exists {
+			aud, ok = audVal.(string)
+			if !ok {
+				return nil, "", "", errors.Configuration.Message("invalid value type, string expected (claims / aud)")
+			}
+		}
+	}
+
+	return claims, iss, aud, nil
+}
+
+// validateClaims validates the token claims against the list of required claims and the expected claims values
 func (j *JWT) validateClaims(token *jwt.Token, claims map[string]interface{}) (map[string]interface{}, error) {
 	var tokenClaims jwt.MapClaims
 	if tc, ok := token.Claims.(jwt.MapClaims); ok {
@@ -322,12 +346,12 @@ func (j *JWT) validateClaims(token *jwt.Token, claims map[string]interface{}) (m
 	}
 
 	if tokenClaims == nil {
-		return nil, errors.JwtTokenInvalid.Message("token has no claims")
+		return nil, fmt.Errorf("token has no claims")
 	}
 
 	for _, key := range j.claimsRequired {
 		if _, ok := tokenClaims[key]; !ok {
-			return nil, errors.JwtTokenInvalid.Message("required claim is missing: " + key)
+			return nil, fmt.Errorf("required claim is missing: " + key)
 		}
 	}
 
@@ -338,11 +362,11 @@ func (j *JWT) validateClaims(token *jwt.Token, claims map[string]interface{}) (m
 
 		val, exist := tokenClaims[k]
 		if !exist {
-			return nil, errors.JwtTokenInvalid.Message("required claim is missing: " + k)
+			return nil, fmt.Errorf("required claim is missing: " + k)
 		}
 
 		if val != v {
-			return nil, errors.JwtTokenInvalid.Messagef("unexpected value for claim %s: %q, expected %q", k, val, v)
+			return nil, fmt.Errorf("unexpected value for claim %s, got %q, expected %q", k, val, v)
 		}
 	}
 	return tokenClaims, nil
@@ -360,7 +384,7 @@ func (j *JWT) getScopeValues(tokenClaims map[string]interface{}, log *logrus.Ent
 	return scopeValues
 }
 
-const warnInvalidValueMsg = "invalid %s claim value type, ignoring claim, value: %#v"
+const warnInvalidValueMsg = "invalid %s claim value type, ignoring claim, value %#v"
 
 func (j *JWT) addScopeValueFromScope(tokenClaims map[string]interface{}, scopeValues []string, log *logrus.Entry) []string {
 	if j.scopeClaim == "" {
@@ -498,10 +522,11 @@ func getBearer(val string) (string, error) {
 	if strings.HasPrefix(strings.ToLower(val), bearer) {
 		return strings.Trim(val[len(bearer):], " "), nil
 	}
-	return "", errors.JwtTokenExpired.Message("bearer required with authorization header")
+	return "", fmt.Errorf("bearer required with authorization header")
 }
 
-func newParser(algos []acjwt.Algorithm, claims map[string]interface{}) (*jwt.Parser, error) {
+// newParser creates a new parser with issuer/audience validation if configured via iss/aud in expected claims
+func newParser(algos []acjwt.Algorithm, iss, aud string) *jwt.Parser {
 	var algorithms []string
 	for _, a := range algos {
 		algorithms = append(algorithms, a.String())
@@ -511,28 +536,16 @@ func newParser(algos []acjwt.Algorithm, claims map[string]interface{}) (*jwt.Par
 		jwt.WithLeeway(time.Second),
 	}
 
-	if claims == nil {
-		options = append(options, jwt.WithoutAudienceValidation())
-		return jwt.NewParser(options...), nil
-	}
-
-	if iss, ok := claims["iss"]; ok {
-		if err := isStringType(iss); err != nil {
-			return nil, fmt.Errorf("iss: %w", err)
-		}
-		options = append(options, jwt.WithIssuer(iss.(string)))
-	}
-
-	if aud, ok := claims["aud"]; ok {
-		if err := isStringType(aud); err != nil {
-			return nil, fmt.Errorf("aud: %w", err)
-		}
-		options = append(options, jwt.WithAudience(aud.(string)))
+	if aud != "" {
+		options = append(options, jwt.WithAudience(aud))
 	} else {
 		options = append(options, jwt.WithoutAudienceValidation())
 	}
+	if iss != "" {
+		options = append(options, jwt.WithIssuer(iss))
+	}
 
-	return jwt.NewParser(options...), nil
+	return jwt.NewParser(options...)
 }
 
 // parsePublicPEMKey tries to parse all supported publicKey variations which
@@ -572,12 +585,3 @@ func parsePublicPEMKey(key []byte) (pub interface{}, err error) {
 	}
 	return pubKey, nil
 }
-
-func isStringType(val interface{}) error {
-	switch val.(type) {
-	case string:
-		return nil
-	default:
-		return fmt.Errorf("invalid value type")
-	}
-}
diff --git a/docs/ERRORS.md b/docs/ERRORS.md
index 3b430c60..898362b5 100644
--- a/docs/ERRORS.md
+++ b/docs/ERRORS.md
@@ -64,16 +64,16 @@ All errors have a specific type. You can find it in the log field `error_type`.
 
 ### Access control error types
 
-| Type (and super types)                          | Description                                                                                      | Default handling                                                            |
-|:------------------------------------------------|:-------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------|
-| `basic_auth`                                    | All `basic_auth` related errors, e.g. unknown user or wrong password.                            | Send error template with status `401` and `WWW-Authenticate: Basic` header. |
-| `basic_auth_credentials_missing` (`basic_auth`) | Client does not provide any credentials.                                                         | Send error template with status `401` and `WWW-Authenticate: Basic` header. |
-| `jwt`                                           | All `jwt` related errors.                                                                        | Send error template with status `403`.                                      |
-| `jwt_token_missing` (`jwt`)                     | No token provided with configured token source.                                                  | Send error template with status `401`.                                      |
-| `jwt_token_expired` (`jwt`)                     | Given token is valid but expired.                                                                | Send error template with status `403`.                                      |
-| `jwt_token_invalid` (`jwt`)                     | The token is not sufficient, e.g. because required claims are missing or have unexpected values. | Send error template with status `403`.                                      |
-| `saml` (or `saml2`)                             | All `saml` related errors                                                                        | Send error template with status `403`.                                      |
-| `oauth2`                                        | All `beta_oauth2`/`oidc` related errors                                                          | Send error template with status `403`.                                      |
+| Type (and super types)                          | Description                                                                                                                  | Default handling                                                            |
+|:------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------|
+| `basic_auth`                                    | All `basic_auth` related errors, e.g. unknown user or wrong password.                                                        | Send error template with status `401` and `WWW-Authenticate: Basic` header. |
+| `basic_auth_credentials_missing` (`basic_auth`) | Client does not provide any credentials.                                                                                     | Send error template with status `401` and `WWW-Authenticate: Basic` header. |
+| `jwt`                                           | All `jwt` related errors.                                                                                                    | Send error template with status `403`.                                      |
+| `jwt_token_missing` (`jwt`)                     | No token provided with configured token source.                                                                              | Send error template with status `401`.                                      |
+| `jwt_token_expired` (`jwt`)                     | Given token is valid but expired.                                                                                            | Send error template with status `403`.                                      |
+| `jwt_token_invalid` (`jwt`)                     | The token is syntactically not a JWT, or not sufficient, e.g. because required claims are missing or have unexpected values. | Send error template with status `403`.                                      |
+| `saml` (or `saml2`)                             | All `saml` related errors                                                                                                    | Send error template with status `403`.                                      |
+| `oauth2`                                        | All `beta_oauth2`/`oidc` related errors                                                                                      | Send error template with status `403`.                                      |
 
 ### API and endpoint error types
 


Test patch:
diff --git a/accesscontrol/jwt_test.go b/accesscontrol/jwt_test.go
index 798f7e65..d40390c8 100644
--- a/accesscontrol/jwt_test.go
+++ b/accesscontrol/jwt_test.go
@@ -181,36 +181,46 @@ func Test_JWT_Validate(t *testing.T) {
 		}
 
 		tests := []struct {
-			name    string
-			fields  fields
-			req     *http.Request
-			wantErr bool
+			name        string
+			fields      fields
+			req         *http.Request
+			wantErrKind string
 		}{
+			{"src: header /w no authorization header", fields{
+				algorithm: algo,
+				source:    ac.NewJWTSource("", "Authorization", nil),
+				pubKey:    pubKeyBytes,
+			}, httptest.NewRequest(http.MethodGet, "/", nil), "jwt_token_missing"},
+			{"src: header /w different auth-scheme", fields{
+				algorithm: algo,
+				source:    ac.NewJWTSource("", "Authorization", nil),
+				pubKey:    pubKeyBytes,
+			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "Basic qbqnb"), "jwt_token_missing"},
 			{"src: header /w empty bearer", fields{
 				algorithm: algo,
 				source:    ac.NewJWTSource("", "Authorization", nil),
 				pubKey:    pubKeyBytes,
-			}, httptest.NewRequest(http.MethodGet, "/", nil), true},
+			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR"), "jwt_token_missing"},
 			{"src: header /w valid bearer", fields{
 				algorithm: algo,
 				source:    ac.NewJWTSource("", "Authorization", nil),
 				pubKey:    pubKeyBytes,
-			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token), false},
+			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token), ""},
 			{"src: header /w no cookie", fields{
 				algorithm: algo,
 				source:    ac.NewJWTSource("token", "", nil),
 				pubKey:    pubKeyBytes,
-			}, httptest.NewRequest(http.MethodGet, "/", nil), true},
+			}, httptest.NewRequest(http.MethodGet, "/", nil), "jwt_token_missing"},
 			{"src: header /w empty cookie", fields{
 				algorithm: algo,
 				source:    ac.NewJWTSource("token", "", nil),
 				pubKey:    pubKeyBytes,
-			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "token", ""), true},
+			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "token", ""), "jwt_token_missing"},
 			{"src: header /w valid cookie", fields{
 				algorithm: algo,
 				source:    ac.NewJWTSource("token", "", nil),
 				pubKey:    pubKeyBytes,
-			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "token", token), false},
+			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "token", token), ""},
 			{"src: header /w valid bearer & claims", fields{
 				algorithm: algo,
 				claims: map[string]string{
@@ -220,7 +230,17 @@ func Test_JWT_Validate(t *testing.T) {
 				claimsRequired: []string{"aud"},
 				source:         ac.NewJWTSource("", "Authorization", nil),
 				pubKey:         pubKeyBytes,
-			}, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), false},
+			}, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), ""},
+			{"src: header /w valid bearer & wrong audience", fields{
+				algorithm: algo,
+				claims: map[string]string{
+					"aud":     "paul",
+					"test123": "value123",
+				},
+				claimsRequired: []string{"aud"},
+				source:         ac.NewJWTSource("", "Authorization", nil),
+				pubKey:         pubKeyBytes,
+			}, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), "jwt_token_invalid"},
 			{"src: header /w valid bearer & w/o claims", fields{
 				algorithm: algo,
 				claims: map[string]string{
@@ -229,7 +249,7 @@ func Test_JWT_Validate(t *testing.T) {
 				},
 				source: ac.NewJWTSource("", "Authorization", nil),
 				pubKey: pubKeyBytes,
-			}, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), true},
+			}, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), "jwt_token_invalid"},
 			{"src: header /w valid bearer & w/o required claims", fields{
 				algorithm: algo,
 				claims: map[string]string{
@@ -238,7 +258,7 @@ func Test_JWT_Validate(t *testing.T) {
 				claimsRequired: []string{"exp"},
 				source:         ac.NewJWTSource("", "Authorization", nil),
 				pubKey:         pubKeyBytes,
-			}, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), true},
+			}, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), "jwt_token_invalid"},
 			{
 				"token_value number",
 				fields{
@@ -247,7 +267,7 @@ func Test_JWT_Validate(t *testing.T) {
 					pubKey:    pubKeyBytes,
 				},
 				setContext(httptest.NewRequest(http.MethodGet, "/", nil)),
-				true,
+				"jwt_token_invalid",
 			},
 			{
 				"token_value string",
@@ -259,7 +279,7 @@ func Test_JWT_Validate(t *testing.T) {
 					pubKey:         pubKeyBytes,
 				},
 				setContext(httptest.NewRequest(http.MethodGet, "/", nil)),
-				false,
+				"",
 			},
 		}
 		for _, tt := range tests {
@@ -283,11 +303,17 @@ func Test_JWT_Validate(t *testing.T) {
 
 				tt.req = tt.req.WithContext(context.WithValue(context.Background(), request.LogEntry, log.WithContext(context.Background())))
 
-				if err = j.Validate(tt.req); (err != nil) != tt.wantErr {
-					subT.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
+				errKind := ""
+				err = j.Validate(tt.req)
+				if err != nil {
+					cErr := err.(*errors.Error)
+					errKind = cErr.Kinds()[0]
+				}
+				if errKind != tt.wantErrKind {
+					subT.Errorf("Validate() error kind does not match; want: %q, got: %q", tt.wantErrKind, errKind)
 				}
 
-				if !tt.wantErr && tt.fields.claims != nil {
+				if tt.wantErrKind == "" && tt.fields.claims != nil {
 					acMap := tt.req.Context().Value(request.AccessControls).(map[string]interface{})
 					if claims, ok := acMap["test_ac"]; !ok {
 						subT.Errorf("Expected a configured access control name within request context")
@@ -379,7 +405,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			true,
 			"",
 			nil,
-			"invalid scope claim value type, ignoring claim, value: true",
+			"invalid scope claim value type, ignoring claim, value true",
 			noScope,
 		},
 		{
@@ -388,7 +414,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			1.23,
 			"",
 			nil,
-			"invalid scope claim value type, ignoring claim, value: 1.23",
+			"invalid scope claim value type, ignoring claim, value 1.23",
 			noScope,
 		},
 		{
@@ -397,7 +423,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			[]bool{true, false},
 			"",
 			nil,
-			"invalid scope claim value type, ignoring claim, value: []interface {}{true, false}",
+			"invalid scope claim value type, ignoring claim, value []interface {}{true, false}",
 			noScope,
 		},
 		{
@@ -406,7 +432,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			[]int{1, 2},
 			"",
 			nil,
-			"invalid scope claim value type, ignoring claim, value: []interface {}{1, 2}",
+			"invalid scope claim value type, ignoring claim, value []interface {}{1, 2}",
 			noScope,
 		},
 		{
@@ -415,7 +441,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			[]interface{}{"eins", 2},
 			"",
 			nil,
-			`invalid scope claim value type, ignoring claim, value: []interface {}{"eins", 2}`,
+			`invalid scope claim value type, ignoring claim, value []interface {}{"eins", 2}`,
 			noScope,
 		},
 		{
@@ -424,7 +450,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			map[string]interface{}{"foo": 1, "bar": 1},
 			"",
 			nil,
-			`invalid scope claim value type, ignoring claim, value: map[string]interface {}{"bar":1, "foo":1}`,
+			`invalid scope claim value type, ignoring claim, value map[string]interface {}{"bar":1, "foo":1}`,
 			noScope,
 		},
 		{
@@ -496,7 +522,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			nil,
 			"roles",
 			true,
-			"invalid roles claim value type, ignoring claim, value: true",
+			"invalid roles claim value type, ignoring claim, value true",
 			[]string{"default"},
 		},
 		{
@@ -505,7 +531,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			nil,
 			"roles",
 			1.23,
-			"invalid roles claim value type, ignoring claim, value: 1.23",
+			"invalid roles claim value type, ignoring claim, value 1.23",
 			[]string{"default"},
 		},
 		{
@@ -514,7 +540,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			nil,
 			"roles",
 			[]bool{true, false},
-			"invalid roles claim value type, ignoring claim, value: []interface {}{true, false}",
+			"invalid roles claim value type, ignoring claim, value []interface {}{true, false}",
 			[]string{"default"},
 		},
 		{
@@ -523,7 +549,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			nil,
 			"roles",
 			[]int{1, 2},
-			"invalid roles claim value type, ignoring claim, value: []interface {}{1, 2}",
+			"invalid roles claim value type, ignoring claim, value []interface {}{1, 2}",
 			[]string{"default"},
 		},
 		{
@@ -532,7 +558,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			nil,
 			"roles",
 			[]interface{}{"user1", 2},
-			`invalid roles claim value type, ignoring claim, value: []interface {}{"user1", 2}`,
+			`invalid roles claim value type, ignoring claim, value []interface {}{"user1", 2}`,
 			[]string{"default"},
 		},
 		{
@@ -541,7 +567,7 @@ func Test_JWT_yields_scopes(t *testing.T) {
 			nil,
 			"roles",
 			map[string]interface{}{"foo": 1, "bar": 1},
-			`invalid roles claim value type, ignoring claim, value: map[string]interface {}{"bar":1, "foo":1}`,
+			`invalid roles claim value type, ignoring claim, value map[string]interface {}{"bar":1, "foo":1}`,
 			[]string{"default"},
 		},
 		{
diff --git a/server/http_endpoints_test.go b/server/http_endpoints_test.go
index 10c6dc4e..c83cebdb 100644
--- a/server/http_endpoints_test.go
+++ b/server/http_endpoints_test.go
@@ -916,13 +916,13 @@ func TestEndpointACBufferOptions(t *testing.T) {
 	}
 
 	for _, tc := range []testcase{
-		{"with ac (token in-form_body) and wrong token", "/in-form_body", invalidToken, urlencoded, "application/x-www-form-urlencoded", http.StatusForbidden, "jwt"},
+		{"with ac (token in-form_body) and wrong token", "/in-form_body", invalidToken, urlencoded, "application/x-www-form-urlencoded", http.StatusForbidden, "jwt_token_invalid"},
 		{"with ac (token in-form_body) and without token", "/in-form_body", "", urlencoded, "application/x-www-form-urlencoded", http.StatusUnauthorized, "jwt_token_missing"},
 		{"with ac (token in-form_body) and valid token", "/in-form_body", validToken, urlencoded, "application/x-www-form-urlencoded", http.StatusOK, ""},
-		{"with ac (token in-json_body) and wrong token", "/in-json_body", invalidToken, jsonFn, "application/json", http.StatusForbidden, "jwt"},
+		{"with ac (token in-json_body) and wrong token", "/in-json_body", invalidToken, jsonFn, "application/json", http.StatusForbidden, "jwt_token_invalid"},
 		{"with ac (token in-json_body) and without token", "/in-json_body", "", jsonFn, "application/json", http.StatusUnauthorized, "jwt_token_missing"},
 		{"with ac (token in-json_body) and valid token", "/in-json_body", validToken, jsonFn, "application/json", http.StatusOK, ""},
-		{"with ac (token in-body) and wrong token", "/in-body", invalidToken, plain, "text/plain", http.StatusForbidden, "jwt"},
+		{"with ac (token in-body) and wrong token", "/in-body", invalidToken, plain, "text/plain", http.StatusForbidden, "jwt_token_invalid"},
 		{"with ac (token in-body) and without token", "/in-body", "", plain, "text/plain", http.StatusUnauthorized, "jwt_token_missing"},
 		{"with ac (token in-body) and valid token", "/in-body", validToken, plain, "text/plain", http.StatusOK, ""},
 		{"without ac", "/without-ac", "", nil, "text/plain", http.StatusOK, ""},