gofr-dev/gofr · error

invalid claim structure

Error message

invalid claim structure

What it means

errInvalidClaimStructure is returned by extractNestedClaim when a dot-notation path traverses into a value that is neither map[string]any nor jwt.MapClaims at its final segment — i.e. the intermediate claim is a scalar/array but the path keeps navigating as if it were an object. The message includes the path prefix where the structure broke. It signals a mismatch between the configured dot path and the token's nested claim shape.

Source

Thrown at pkg/gofr/rbac/middleware.go:74

	errClaimPathNotFound = errors.New("claim path not found")

	// errInvalidArrayNotation is returned when array notation is invalid.
	errInvalidArrayNotation = errors.New("invalid array notation")

	// errInvalidArrayIndex is returned when array index is invalid.
	errInvalidArrayIndex = errors.New("invalid array index")

	// errClaimKeyNotFound is returned when a claim key is not found.
	errClaimKeyNotFound = errors.New("claim key not found")

	// errClaimValueNotArray is returned when a claim value is not an array.
	errClaimValueNotArray = errors.New("claim value is not an array")

	// errArrayIndexOutOfBounds is returned when array index is out of bounds.
	errArrayIndexOutOfBounds = errors.New("array index out of bounds")

	// errInvalidClaimStructure is returned when claim structure is invalid.
	errInvalidClaimStructure = errors.New("invalid claim structure")

	// errAuthorizationError is returned as a generic error message for unknown errors in traces.
	errAuthorizationError = errors.New("authorization error")
)

// Middleware creates an HTTP middleware function that enforces RBAC authorization.
// It extracts the user's role and checks if the role is allowed for the requested route.
//
//nolint:gocognit,gocyclo // Middleware complexity is acceptable due to multiple authorization paths
func Middleware(config *Config) func(handler http.Handler) http.Handler {
	return func(handler http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// If config is nil, allow all requests (fail open)
			if config == nil {
				handler.ServeHTTP(w, r)

				return
			}

View on GitHub (pinned to 187eb24962)

Solutions

  1. Decode the token and check the type at each path segment; use array notation "key[0]" where the value is a list instead of dot notation.
  2. Correct JWTClaimPath so every intermediate segment is a JSON object, e.g. "resource_access.myclient.roles[0]" for nested Keycloak claims.
  3. Combine notations correctly: array index and dot segments can appear in one path ("a.b[0]"), but a dot after an array element is only valid if that element is itself an object.
  4. For mixed shapes, normalize the token or handle both in a custom ErrorHandler.

Example fix

// before ("realm_access.roles" is an array, not an object)
config.JWTClaimPath = "realm_access.roles.admin"
// after
config.JWTClaimPath = "realm_access.roles[0]"
Defensive patterns

Strategy: validation

Validate before calling

func pathNavigable(claims jwt.MapClaims, path string) bool {
    var cur any = claims
    for _, part := range strings.Split(path, ".") {
        part = strings.SplitN(part, "[", 2)[0]
        m, ok := cur.(map[string]any)
        if !ok { if mc, ok2 := cur.(jwt.MapClaims); ok2 { m, ok = mc, true } }
        if !ok { return false }
        v, exists := m[part]
        if !exists { return false }
        cur = v
    }
    return true
}

Type guard

func isClaimMap(v any) bool {
    switch v.(type) {
    case map[string]any, jwt.MapClaims:
        return true
    }
    return false
}

Try / catch

if err != nil && strings.Contains(err.Error(), "invalid claim structure") {
    logger.Warn("dot path traverses a non-object claim; use array notation", "err", err)
    http.Error(w, "unauthorized", http.StatusUnauthorized)
    return
}

Prevention

When it happens

Trigger: JWTClaimPath like "roles.admin" where "roles" is an array ([]any) rather than a map — the switch hits the default branch at the last segment and returns errInvalidClaimStructure; also "name.first" where "name" is a plain string.

Common situations: Path written assuming a nested object while the IdP emits an array of roles (needing "roles[0]" notation instead); mixing array and dot notation incorrectly (dot notation cannot index arrays); migrating between IdPs with differently shaped role claims; typos where the last segment should have been a key inside a map.

Related errors


AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01). Data as JSON: /api/errors/d22a262268cb9a69. Report an issue: GitHub.