diff --git a/authorize/evaluator/headers_evaluator_evaluation.go b/authorize/evaluator/headers_evaluator_evaluation.go index d61215b6a..c5f9c30a1 100644 --- a/authorize/evaluator/headers_evaluator_evaluation.go +++ b/authorize/evaluator/headers_evaluator_evaluation.go @@ -280,6 +280,12 @@ func (e *headersEvaluatorEvaluation) getJWTPayloadGroups(ctx context.Context) [] s, _ := e.getSessionOrServiceAccount(ctx) groups, _ := getClaimStringSlice(s, "groups") + if groups == nil { + // If there are no groups, marshal this claim as an empty list rather than a JSON null, + // for better compatibility with third-party libraries. + // See https://github.com/pomerium/pomerium/issues/5393 for one example. + groups = []string{} + } return groups } diff --git a/authorize/evaluator/headers_evaluator_test.go b/authorize/evaluator/headers_evaluator_test.go index c9194d7bd..3e365695a 100644 --- a/authorize/evaluator/headers_evaluator_test.go +++ b/authorize/evaluator/headers_evaluator_test.go @@ -281,6 +281,28 @@ func TestHeadersEvaluator(t *testing.T) { assert.Equal(t, []any{"g1", "g2", "g3", "g4", "GROUP1", "GROUP2", "GROUP3", "GROUP4"}, claims["groups"]) }) + t.Run("jwt no groups", func(t *testing.T) { + t.Parallel() + + output, err := eval(t, + []protoreflect.ProtoMessage{ + &session.Session{Id: "s1", UserId: "u1", Claims: map[string]*structpb.ListValue{ + "name": {Values: []*structpb.Value{ + structpb.NewStringValue("User Name"), + }}, + }}, + }, + &HeadersRequest{ + Session: RequestSession{ID: "s1"}, + }) + require.NoError(t, err) + jwtHeader := output.Headers.Get("X-Pomerium-Jwt-Assertion") + var decoded map[string]any + err = json.Unmarshal(decodeJWSPayload(t, jwtHeader), &decoded) + require.NoError(t, err) + assert.Equal(t, []any{}, decoded["groups"]) + }) + t.Run("set_request_headers", func(t *testing.T) { output, err := eval(t, []proto.Message{