diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go
index 14329c0040d8a7bad3121d018752c66192f9d9b9..00e9dc48e7e14031c6748ecfecc68dead8c02b37 100644
--- a/connector/oidc/oidc.go
+++ b/connector/oidc/oidc.go
@@ -87,6 +87,21 @@ type Config struct {
 		// Configurable key which contains the groups claims
 		GroupsKey string `json:"groups"` // defaults to "groups"
 	} `json:"claimMapping"`
+
+	// List of new claim to generate based on concatinate existing claims
+	ClaimConcatenations []ClaimConcatenation `json:"claimConcatenations"`
+}
+
+// List of groups claim elements to create by concatenating other claims
+type ClaimConcatenation struct {
+	// List of claim to join together
+	ClaimList []string `json:"claimList"`
+
+	// String to separate the claims
+	Delimiter string `json:"delimiter"`
+
+	// String to place before the first claim
+	Prefix string `json:"prefix"`
 }
 
 // Domains that don't support basic auth. golang.org/x/oauth2 has an internal
@@ -189,6 +204,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
 		preferredUsernameKey:      c.ClaimMapping.PreferredUsernameKey,
 		emailKey:                  c.ClaimMapping.EmailKey,
 		groupsKey:                 c.ClaimMapping.GroupsKey,
+		claimConcatenations:       c.ClaimConcatenations,
 	}, nil
 }
 
@@ -216,6 +232,7 @@ type oidcConnector struct {
 	preferredUsernameKey      string
 	emailKey                  string
 	groupsKey                 string
+	claimConcatenations       []ClaimConcatenation
 }
 
 func (c *oidcConnector) Close() error {
@@ -416,6 +433,26 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
 		}
 	}
 
+	for _, cc := range c.claimConcatenations {
+		newElement := ""
+		for _, clm := range cc.ClaimList {
+			// Non string claim value are ignored, concatenating them doesn't really make any sense
+			if claimValue, ok := claims[clm].(string); ok {
+				// Removing the delimiier string from the concatenated claim to ensure resulting claim structure
+				// is in full control of Dex operator
+				claimCleanedValue := strings.ReplaceAll(claimValue, cc.Delimiter, "")
+				if newElement == "" {
+					newElement = cc.Prefix + cc.Delimiter + claimCleanedValue
+				} else {
+					newElement = newElement + cc.Delimiter + claimCleanedValue
+				}
+			}
+		}
+		if newElement != "" {
+			groups = append(groups, newElement)
+		}
+	}
+
 	cd := connectorData{
 		RefreshToken: []byte(token.RefreshToken),
 	}
diff --git a/connector/oidc/oidc_test.go b/connector/oidc/oidc_test.go
index 5c5208a60e7990310633da8d575916100349af82..2f19d9674f04189b0796071e45c86e24cc8a386a 100644
--- a/connector/oidc/oidc_test.go
+++ b/connector/oidc/oidc_test.go
@@ -62,6 +62,7 @@ func TestHandleCallback(t *testing.T) {
 		expectPreferredUsername   string
 		expectedEmailField        string
 		token                     map[string]interface{}
+		claimConcatenations       []ClaimConcatenation
 	}{
 		{
 			name:               "simpleCase",
@@ -288,6 +289,66 @@ func TestHandleCallback(t *testing.T) {
 				"email_verified": true,
 			},
 		},
+		{
+			name:               "concatinateClaim",
+			userIDKey:          "", // not configured
+			userNameKey:        "", // not configured
+			expectUserID:       "subvalue",
+			expectUserName:     "namevalue",
+			expectGroups:       []string{"group1", "gh::acme::pipeline-one", "tfe-acme-foobar", "bk-emailvalue"},
+			expectedEmailField: "emailvalue",
+			claimConcatenations: []ClaimConcatenation{
+				{
+					ClaimList: []string{
+						"organization",
+						"pipeline",
+					},
+					Delimiter: "::",
+					Prefix:    "gh",
+				},
+				{
+					ClaimList: []string{
+						"non-existing1",
+						"non-existing2",
+					},
+					Delimiter: "::",
+					Prefix:    "tfe",
+				},
+				{
+					ClaimList: []string{
+						"organization",
+						"claim-with-delimiter",
+					},
+					Delimiter: "-",
+					Prefix:    "tfe",
+				},
+				{
+					ClaimList: []string{
+						"non-string-claim",
+						"non-string-claim2",
+						"email",
+					},
+					Delimiter: "-",
+					Prefix:    "bk",
+				},
+			},
+
+			token: map[string]interface{}{
+				"sub":                  "subvalue",
+				"name":                 "namevalue",
+				"groups":               "group1",
+				"organization":         "acme",
+				"pipeline":             "pipeline-one",
+				"email":                "emailvalue",
+				"email_verified":       true,
+				"claim-with-delimiter": "foo-bar",
+				"non-string-claim": []string{
+					"element1",
+					"element2",
+				},
+				"non-string-claim2": 666,
+			},
+		},
 	}
 
 	for _, tc := range tests {
@@ -319,6 +380,7 @@ func TestHandleCallback(t *testing.T) {
 				InsecureEnableGroups:      true,
 				BasicAuthUnsupported:      &basicAuth,
 				OverrideClaimMapping:      tc.overrideClaimMapping,
+				ClaimConcatenations:       tc.claimConcatenations,
 			}
 			config.ClaimMapping.PreferredUsernameKey = tc.preferredUsernameKey
 			config.ClaimMapping.EmailKey = tc.emailKey