Repository: SAP/cloud-security-xsuaa-integration
Pull request #389 (2020-10-08 15:27:39)

Change request: Replace "cid" with "azp" / "aud"
Instead of `cid` (OAuth2 standard) use OIDC standard, that means
- return `azp` claim if claim exists with non-null value 
- return `aud` claim (which is single String or StringList with only one entry)
- return error in all the other cases

Spec https://openid.net/specs/openid-connect-core-1_0.html


hassler-d: Hi @nenaraab can you take a look at it again? We went all in on `azp` in the `JwtGenerator(s)` and we weren't sure if this is the right approach or if the `azp` parameter should just be used internally.
liga-oz: deploy_and_test script.py execution was successful

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15df4ddf..e87a52f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,13 @@
 
 All notable changes to this project will be documented in this file.
 
+## 2.8.0
+- [java-security] 
+    -`getClientId()` method was added to `Token` interface. `getClientId()` method should be used instead of `getClaimAsString(TokenClaims.CLIENT_ID)`
+- [spring-xsuaa] `getClientId()` method implementation of `Token` interface has been changed. Using "azp" and as fallback "aud" and "cid" claims to obtain client id.
+
 ## 2.7.8
 - [java-security-test] Supports JUnit 5 Tests with `XsuaaExtension`, `IasExtension` and `SecurityTestExtension` as documented [here](/java-security-test#junit-5).
-- [java-security] Does not fail in case of Xsuaa services of type: `apiacess` (#382).
 - [spring-xsuaa-starter] Upgrade Spring versions:
   - spring.boot.version: 2.3.1.RELEASE --> 2.3.4.RELEASE
   - spring.core.version: 5.2.8.RELEASE --> 5.2.9.RELEASE
@@ -13,7 +17,9 @@ All notable changes to this project will be documented in this file.
   - javax.servlet-api.version 3.0.1 --> 3.1.0
   - Apache HTTP client 4.5.8 --> 4.5.9
   - Wiremock 2.25.1 --> 2.27.2
-  
+- [java-security] 
+    - Does not fail in case of Xsuaa services of type: `apiacess` (#382).
+
 ## 2.7.7
 - [spring-xsuaa] Update Spring versions
   - spring.core.version: 5.2.7.RELEASE --> 5.2.8.RELEASE
diff --git a/java-api/src/main/java/com/sap/cloud/security/token/InvalidTokenException.java b/java-api/src/main/java/com/sap/cloud/security/token/InvalidTokenException.java
new file mode 100644
index 00000000..18314185
--- /dev/null
+++ b/java-api/src/main/java/com/sap/cloud/security/token/InvalidTokenException.java
@@ -0,0 +1,10 @@
+package com.sap.cloud.security.token;
+
+/**
+ * Runtime exception during token validation.
+ */
+public class InvalidTokenException extends RuntimeException {
+	public InvalidTokenException(String message) {
+		super(message);
+	}
+}
diff --git a/java-api/src/main/java/com/sap/cloud/security/token/Token.java b/java-api/src/main/java/com/sap/cloud/security/token/Token.java
index 6f51316b..651b4995 100644
--- a/java-api/src/main/java/com/sap/cloud/security/token/Token.java
+++ b/java-api/src/main/java/com/sap/cloud/security/token/Token.java
@@ -149,4 +149,14 @@ public interface Token {
 	 */
 	String getZoneId();
 
+	/**
+	 * Returns the OAuth2 client identifier of the authentication token if present.
+	 * Following OpenID Connect 1.0 standard specifications, client identifier is
+	 * obtained from "azp" claim if present or when "azp" is not present from "aud"
+	 * claim, but only in case there is one audience.
+	 * 
+	 * @return the OAuth client ID.
+	 */
+	String getClientId();
+
 }
diff --git a/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java b/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java
index 59267874..9f0f457b 100644
--- a/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java
+++ b/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java
@@ -5,6 +5,7 @@ package com.sap.cloud.security.token;
  * https://tools.ietf.org/html/rfc7519#section-4
  */
 public final class TokenClaims {
+
 	private TokenClaims() {
 		throw new IllegalStateException("Utility class");
 	}
@@ -21,6 +22,7 @@ public final class TokenClaims {
 	public static final String SAP_GLOBAL_USER_ID = "user_uuid";
 	public static final String SAP_GLOBAL_ZONE_ID = "zone_uuid"; // tenant GUID
 	public static final String GROUPS = "groups"; // scim groups
+	public static final String AUTHORIZATION_PARTY = "azp"; // Authorization party contains OAuth client identifier
 
 	public final class XSUAA {
 
@@ -30,7 +32,11 @@ public final class TokenClaims {
 		public static final String ORIGIN = "origin";
 		public static final String GRANT_TYPE = "grant_type"; // OAuth grant type used for token creation
 		public static final String ZONE_ID = "zid"; // tenant GUID -> SAP_GLOBAL_ZONE_ID
-		public static final String CLIENT_ID = "cid"; // OAuth client identifier
+		/**
+		 * @deprecated Use {@link Token#getClientId} instead.
+		 */
+		@Deprecated
+		public static final String CLIENT_ID = "cid"; // avoid using directly, make use of Token#getClientId() instead
 		public static final String SCOPES = "scope"; // list of scopes including app id, e.g. "my-app!t123.Display"
 		public static final String ISSUED_AT = "iat";
 		public static final String EXTERNAL_ATTRIBUTE = "ext_attr";
diff --git a/java-security/pom.xml b/java-security/pom.xml
index e2e44cee..b9f680b3 100644
--- a/java-security/pom.xml
+++ b/java-security/pom.xml
@@ -66,6 +66,16 @@
 			<artifactId>junit</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>org.junit.jupiter</groupId>
+			<artifactId>junit-jupiter-engine</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.junit.jupiter</groupId>
+			<artifactId>junit-jupiter-params</artifactId>
+			<scope>test</scope>
+		</dependency>
 		<dependency>
 			<groupId>org.mockito</groupId>
 			<artifactId>mockito-core</artifactId>
diff --git a/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
index 0fa187b2..60d33e3b 100644
--- a/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
+++ b/java-security/src/main/java/com/sap/cloud/security/adapter/spring/SAPOfflineTokenServicesCloud.java
@@ -60,7 +60,7 @@ import java.util.stream.Collectors;
  * <p>
  * When used in conjunction with Java Http Servlets, the
  * {@link HttpServletRequest#getRemoteUser()} will be filled with either the
- * <code>user_name</code> claim of the token or the client id (<code>cid</code>)
+ * <code>user_name</code> claim of the token or the client id (<code>azp</code>)
  * if it is not an user token.
  * </p>
  *
@@ -170,7 +170,7 @@ public class SAPOfflineTokenServicesCloud implements ResourceServerTokenServices
 		}
 		SecurityContext.setToken(token);
 
-		String tokenClientId = token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID);
+		String tokenClientId = token.getClientId();
 		if (LOGGER.isInfoEnabled() && tokenClientId != serviceConfiguration.getClientId()) {
 			LOGGER.info("Creates OAuth2Authentication with token clientId {} which differs from oauth client id {}.",
 					tokenClientId, serviceConfiguration.getClientId());
diff --git a/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java b/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
index f0a98662..27fef44f 100644
--- a/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
+++ b/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
@@ -147,7 +147,7 @@ public class XSUserInfoAdapter implements XSUserInfo {
 
 	@Override
 	public String getClientId() {
-		return getClaimValue(CLIENT_ID);
+		return accessToken.getClientId();
 	}
 
 	@Override
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java b/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java
index d5f247d4..a5d63ce3 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/AbstractToken.java
@@ -5,6 +5,8 @@ import com.sap.cloud.security.json.JsonObject;
 import com.sap.cloud.security.xsuaa.Assertions;
 import com.sap.cloud.security.xsuaa.jwt.Base64JwtDecoder;
 import com.sap.cloud.security.xsuaa.jwt.DecodedJwt;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -12,21 +14,20 @@ import java.security.Principal;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
 import java.util.regex.Pattern;
 
-import static com.sap.cloud.security.token.TokenClaims.EXPIRATION;
-import static com.sap.cloud.security.token.TokenClaims.NOT_BEFORE;
-import static com.sap.cloud.security.token.TokenClaims.XSUAA.ISSUED_AT;
+import static com.sap.cloud.security.token.TokenClaims.*;
+import static com.sap.cloud.security.token.TokenClaims.XSUAA.*;
 
 /**
  * Decodes and parses encoded JSON Web Token (JWT) and provides access to token
  * header parameters and claims.
  */
 public abstract class AbstractToken implements Token {
+
+	private static final Logger LOGGER = LoggerFactory.getLogger(AbstractToken.class);
+
 	private final DecodedJwt decodedJwt;
 	protected final DefaultJsonObject tokenHeader;
 	protected final DefaultJsonObject tokenBody;
@@ -40,7 +41,7 @@ public abstract class AbstractToken implements Token {
 	/**
 	 * Creates a Token object for simple access to the header parameters and its
 	 * claims.
-	 * 
+	 *
 	 * @param jwtToken
 	 *            the encoded JWT token (access_token or id_token), e.g. from the
 	 *            Authorization Header.
@@ -111,7 +112,7 @@ public abstract class AbstractToken implements Token {
 	@Override
 	public Set<String> getAudiences() {
 		Set<String> audiences = new LinkedHashSet<>();
-		audiences.addAll(getClaimAsStringList(TokenClaims.AUDIENCE));
+		audiences.addAll(getClaimAsStringList(AUDIENCE));
 		return audiences;
 	}
 
@@ -162,7 +163,27 @@ public abstract class AbstractToken implements Token {
 
 	@Override
 	public String getZoneId() {
-		return getClaimAsString(TokenClaims.SAP_GLOBAL_ZONE_ID);
+		return getClaimAsString(SAP_GLOBAL_ZONE_ID);
+	}
+
+	@Override
+	public String getClientId() {
+		String clientId = getClaimAsString(AUTHORIZATION_PARTY);
+		if (clientId == null || clientId.trim().isEmpty()) {
+			Set<String> audiences = getAudiences();
+
+			if (audiences.size() == 1) {
+				return audiences.stream().findFirst().get();
+			} else if (hasClaim(CLIENT_ID) && !getClaimAsString(CLIENT_ID).trim()
+					.isEmpty()) { // required for backward compatibility for generated tokens in JUnit tests
+				LOGGER.warn("Usage of 'cid' claim is deprecated and should be replaced by 'azp' or 'aud' claims");
+				return getClaimAsString(CLIENT_ID);
+			}
+			LOGGER.error("Couldn't get client id. Invalid authorized party or audience claims.");
+			throw new InvalidTokenException("Couldn't get client id. Invalid authorized party or audience claims.");
+		} else {
+			return clientId;
+		}
 	}
 
 	@Override
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
index 4470644c..20da0796 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/XsuaaToken.java
@@ -107,7 +107,7 @@ public class XsuaaToken extends AbstractToken implements AccessToken {
 		switch (getGrantType()) {
 		case CLIENT_CREDENTIALS:
 		case CLIENT_X509:
-			principalName = String.format(UNIQUE_CLIENT_NAME_FORMAT, getClaimAsString(CLIENT_ID));
+			principalName = String.format(UNIQUE_CLIENT_NAME_FORMAT, getClientId());
 			break;
 		default:
 			principalName = getUniquePrincipalName(getClaimAsString(ORIGIN), getClaimAsString(USER_NAME));
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidator.java
index 12240962..0b497c1c 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidator.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidator.java
@@ -98,8 +98,8 @@ public class JwtAudienceValidator implements Validator<Token> {
 		}
 
 		if (Service.XSUAA.equals(token.getService())) {
-			if (token.hasClaim(TokenClaims.XSUAA.CLIENT_ID)) {
-				audiences.add(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID));
+			if (token.hasClaim(TokenClaims.AUTHORIZATION_PARTY)) {
+				audiences.add(token.getClientId());
 			}
 			// extract audience (app-id) from scopes
 			if (token.getAudiences().isEmpty()) {
diff --git a/pom.xml b/pom.xml
index 3f5ee819..ea76f2ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -268,8 +268,33 @@
 				<groupId>org.junit</groupId>
 				<artifactId>junit-bom</artifactId>
 				<version>${junit-jupiter.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.junit.jupiter</groupId>
+				<artifactId>junit-jupiter-params</artifactId>
+				<version>${junit-jupiter.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.junit.vintage</groupId>
+				<artifactId>junit-vintage-engine</artifactId>
+				<version>${junit-jupiter.version}</version>
 				<scope>import</scope>
 			</dependency>
+			<dependency>
+				<groupId>org.junit.jupiter</groupId>
+				<artifactId>junit-jupiter-params</artifactId>
+				<version>${junit-jupiter.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.junit.vintage</groupId>
+				<artifactId>junit-vintage-engine</artifactId>
+				<version>${junit-jupiter.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.junit.jupiter</groupId>
+				<artifactId>junit-jupiter-params</artifactId>
+				<version>${junit-jupiter.version}</version>
+			</dependency>
 			<dependency>
 				<groupId>org.hamcrest</groupId>
 				<artifactId>hamcrest-all</artifactId>
diff --git a/spring-xsuaa/pom.xml b/spring-xsuaa/pom.xml
index e8ca5aca..9b853261 100644
--- a/spring-xsuaa/pom.xml
+++ b/spring-xsuaa/pom.xml
@@ -73,6 +73,11 @@
 			<artifactId>junit</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>org.junit.jupiter</groupId>
+			<artifactId>junit-jupiter-params</artifactId>
+			<scope>test</scope>
+		</dependency>
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
 			<artifactId>spring-boot-starter-test</artifactId>
@@ -82,6 +87,10 @@
 					<groupId>com.vaadin.external.google</groupId>
 					<artifactId>android-json</artifactId>
 				</exclusion>
+				<exclusion>
+					<groupId>org.junit.jupiter</groupId>
+					<artifactId>junit-jupiter</artifactId>
+				</exclusion>
 			</exclusions>
 		</dependency>
 		<dependency>
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/Token.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/Token.java
index 72114536..f963e796 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/Token.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/Token.java
@@ -45,7 +45,10 @@ public interface Token extends UserDetails {
 	public String getSubdomain();
 
 	/**
-	 * Returns the OAuth client identifier of the authentication token if present.
+	 * Returns the OAuth2 client identifier of the authentication token if present.
+	 * Following OpenID Connect 1.0 standard specifications, client identifier is
+	 * obtained from "azp" claim if present or when "azp" is not present from "aud"
+	 * claim, but only in case there is one audience.
 	 *
 	 * @return the OAuth client ID.
 	 */
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenClaims.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenClaims.java
index 11d1124e..332104a6 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenClaims.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenClaims.java
@@ -1,5 +1,7 @@
 package com.sap.cloud.security.xsuaa.token;
 
+import com.sap.cloud.security.token.Token;
+
 public final class TokenClaims {
 	private TokenClaims() {
 		throw new IllegalStateException("Utility class");
@@ -7,7 +9,12 @@ public final class TokenClaims {
 
 	public static final String CLAIM_XS_USER_ATTRIBUTES = "xs.user.attributes";
 	public static final String CLAIM_SCOPES = "scope";
+	/**
+	 * @deprecated avoid using directly, make use of Token#getClientId() instead
+	 */
+	@Deprecated
 	public static final String CLAIM_CLIENT_ID = "cid";
+	public static final String CLAIM_AUTHORIZATION_PARTY = "azp";
 	public static final String CLAIM_USER_NAME = "user_name";
 	public static final String CLAIM_GIVEN_NAME = "given_name";
 	public static final String CLAIM_FAMILY_NAME = "family_name";
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/XsuaaToken.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/XsuaaToken.java
index a9603fb5..f93c30e6 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/XsuaaToken.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/XsuaaToken.java
@@ -1,5 +1,6 @@
 package com.sap.cloud.security.xsuaa.token;
 
+import com.sap.cloud.security.token.InvalidTokenException;
 import net.minidev.json.JSONArray;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -12,6 +13,8 @@ import org.springframework.util.Assert;
 import java.time.Instant;
 import java.util.*;
 
+import static com.sap.cloud.security.token.TokenClaims.AUTHORIZATION_PARTY;
+import static com.sap.cloud.security.token.TokenClaims.XSUAA.CLIENT_ID;
 import static com.sap.cloud.security.xsuaa.token.TokenClaims.*;
 import static org.springframework.util.StringUtils.isEmpty;
 
@@ -141,7 +144,24 @@ public class XsuaaToken extends Jwt implements Token {
 	@Override
 	@Nullable
 	public String getClientId() {
-		return getClaimAsString(CLAIM_CLIENT_ID);
+		String clientId = getClaimAsString(AUTHORIZATION_PARTY);
+		if (clientId == null || clientId.trim().isEmpty()) {
+			List<String> audiences = getAudience();
+
+			if (audiences != null && audiences.size() == 1) {
+				return audiences.get(0);
+			} else if (hasClaim(CLIENT_ID) && !getClaimAsString(CLIENT_ID).trim() // required for backward compatibility
+																					// for generated tokens in JUnit
+																					// tests
+					.isEmpty()) {
+				logger.warn("usage of 'cid' claim is deprecated and should be replaced by 'azp' or 'aud' claims");
+				return getClaimAsString(CLIENT_ID);
+			}
+			logger.error("Couldn't get client id. Invalid authorized party or audience claims.");
+			throw new InvalidTokenException("Couldn't get client id. Invalid authorized party or audience claims.");
+		} else {
+			return clientId;
+		}
 	}
 
 	@Override
@@ -224,7 +244,7 @@ public class XsuaaToken extends Jwt implements Token {
 
 	/**
 	 * Check if the authentication token contains a claim, e.g. "email".
-	 * 
+	 *
 	 * @param claim
 	 *            name of the claim
 	 * @return true: attribute exists


Test patch:
diff --git a/java-security-it/src/test/resources/ias-simple/token.json b/java-security-it/src/test/resources/ias-simple/token.json
index 7a323898..f25801a7 100644
--- a/java-security-it/src/test/resources/ias-simple/token.json
+++ b/java-security-it/src/test/resources/ias-simple/token.json
@@ -5,7 +5,7 @@
   },
   "payload": {
     "aud": "T000310",
-    "cid": "T000310",
+    "azp": "T000310",
     "email": "john.doe@email.org",
     "exp": 6974031600,
     "given_name": "john",
diff --git a/java-security-it/src/test/resources/xsa-simple/token.json b/java-security-it/src/test/resources/xsa-simple/token.json
index 5b66da66..3fa5331d 100644
--- a/java-security-it/src/test/resources/xsa-simple/token.json
+++ b/java-security-it/src/test/resources/xsa-simple/token.json
@@ -10,7 +10,6 @@
     ],
     "auth_time": 1579526840,
     "azp": "sb-java-hello-world!i1",
-    "cid": "sb-java-hello-world!i1",
     "client_id": "sb-java-hello-world!i1",
     "email": "XSA_ADMIN@XS1",
     "exp": 1579570287,
diff --git a/java-security-it/src/test/resources/xsuaa/token.json b/java-security-it/src/test/resources/xsuaa/token.json
index ccf47a91..b38a20d8 100644
--- a/java-security-it/src/test/resources/xsuaa/token.json
+++ b/java-security-it/src/test/resources/xsuaa/token.json
@@ -8,7 +8,7 @@
     "aud": [
       "clientId"
     ],
-    "cid": "clientId",
+    "azp": "clientId",
     "exp": "6974031600",
     "ext_attr": {
       "enhancer": "XSUAA",
diff --git a/java-security-test/README.md b/java-security-test/README.md
index 78e8e32e..e817c80c 100644
--- a/java-security-test/README.md
+++ b/java-security-test/README.md
@@ -36,7 +36,7 @@ Using `JwtGenerator` you can create tokens of type [`Token`](/java-security/src/
 ```java
 Token token = JwtGenerator.getInstance(Service.XSUAA)
                                 .withHeaderParameter(TokenHeader.KEY_ID, "key-id") // optional
-                                .withClaimValue(TokenClaims.XSUAA.CLIENT_ID, clientId) // optional
+                                .withClaimValue(TokenClaims.XSUAA.AUTHORIZATION_PARTY, azp) // optional
                                 .createToken();
 
 String authorizationHeaderValue = 'Bearer ' + token.getTokenValue();
@@ -45,7 +45,7 @@ String authorizationHeaderValue = 'Bearer ' + token.getTokenValue();
 ### Unit Test Utilities
 In case you want to test your secured web application as part of your JUnit tests you need to generate JWT tokens and in order to validate the token you need also to mock the jwks endpoint of the identity service e.g. xsuaa. 
 
-The `java-security-test` library uses third-party library [WireMock](http://wiremock.org/docs/getting-started/) to stub outgoing calls to the identity service. Furthermore it pre-configures the `JwtGenerator`, so that the token is signed with a private key which matches the public key provided by the jwks endpoint (on behalf of WireMock). Furthermore you can specify the `clientId` for token generation, that it can be validated by the predefined set of Jwt validators.
+The `java-security-test` library uses third-party library [WireMock](http://wiremock.org/docs/getting-started/) to stub outgoing calls to the identity service. Furthermore it pre-configures the `JwtGenerator`, so that the token is signed with a private key which matches the public key provided by the jwks endpoint (on behalf of WireMock). Furthermore you can specify the `azp` for token generation, that it can be validated by the predefined set of Jwt validators.
 
 Optionally, you can configure `java-security-test` to start an embedded Jetty servlet container that comes equipped with an [authenticator](src/main/java/com/sap/cloud/security/servlet/XsuaaTokenAuthenticator.java). The authenticator checks whether a request is done by an authenticated AND authorized party. You can also add your own servlets to the container. Only requests that contain a valid authorization header will be passed through to the servlet. See the following test code that triggers HTTP requests against the servlet container. One request does not contain the token inside the authorization header and is expected to result in HTTP `401` (unauthenticated). The other contains a valid token and is expected to succeed.
 
diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java b/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java
index 7b494a39..46681cfe 100644
--- a/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java
+++ b/java-security-test/src/main/java/com/sap/cloud/security/test/JwtGenerator.java
@@ -60,7 +60,7 @@ public class JwtGenerator {
 	 * @param service
 	 *            the {@link Service} for which the token should be generated
 	 * @param clientId
-	 *            the client id of the token.
+	 *            the authorization party of the token.
 	 * @return a new {@link JwtGenerator} instance.
 	 */
 	public static JwtGenerator getInstance(Service service, String clientId) {
@@ -133,14 +133,16 @@ public class JwtGenerator {
 		return this;
 	}
 
-	private void setDefaultsForNewToken(String clientId) {
+	private void setDefaultsForNewToken(String azp) {
 		this.signatureAlgorithm = JwtSignatureAlgorithm.RS256;
 		withHeaderParameter(ALGORITHM, JwtSignatureAlgorithm.RS256.value());
-		withClaimValue(TokenClaims.XSUAA.CLIENT_ID, clientId);
+		withClaimValue(TokenClaims.AUTHORIZATION_PARTY, azp);
+		withClaimValue(TokenClaims.XSUAA.CLIENT_ID, azp); // Client Id left for backward compatibility
 		if (service == Service.IAS) {
-			jsonPayload.put(TokenClaims.AUDIENCE, jsonPayload.getString(TokenClaims.XSUAA.CLIENT_ID));
+			jsonPayload.put(TokenClaims.AUDIENCE, azp);
 		} else {
-			jsonPayload.put(TokenClaims.AUDIENCE, Arrays.asList(jsonPayload.getString(TokenClaims.XSUAA.CLIENT_ID)));
+			withClaimValue(TokenClaims.XSUAA.CLIENT_ID, azp); // Client Id left for backward compatibility
+			jsonPayload.put(TokenClaims.AUDIENCE, Arrays.asList(azp));
 		}
 	}
 
@@ -170,7 +172,9 @@ public class JwtGenerator {
 	}
 
 	/**
-	 * Sets the claim with the given name to the given string value.
+	 * Sets the claim with the given name to the given string value. Note: for
+	 * overwriting client Id claim, "azp" claim value should be overwritten instead
+	 * of deprecated "cid"
 	 *
 	 * @param claimName
 	 *            the name of the claim to be set.
@@ -184,7 +188,9 @@ public class JwtGenerator {
 	}
 
 	/**
-	 * Sets the claim with the given name to the given string value.
+	 * Sets the claim with the given name to the given string value. Note: for
+	 * overwriting client Id claim, "azp" claim value should be overwritten instead
+	 * of deprecated "cid"
 	 *
 	 * @param claimName
 	 *            the name of the claim to be set.
@@ -204,7 +210,9 @@ public class JwtGenerator {
 	}
 
 	/**
-	 * Sets the claim with the given name to the given string values.
+	 * Sets the claim with the given name to the given string values. Note: for
+	 * overwriting client Id claim, "azp" claim value should be overwritten instead
+	 * of deprecated "cid"
 	 *
 	 * @param claimName
 	 *            the name of the claim to be set.
@@ -219,7 +227,9 @@ public class JwtGenerator {
 
 	/**
 	 * This method will fill the token with all the claims that are defined inside
-	 * the given file. The file must contain a valid json object.
+	 * the given file. The file must contain a valid json object. Note: for
+	 * overwriting client Id claim, "azp" claim value should be overwritten instead
+	 * of deprecated "cid"
 	 *
 	 * @throws JsonParsingException
 	 *             if the file does not contain a valid json object.
@@ -466,4 +476,4 @@ public class JwtGenerator {
 		return Optional.of(algorithm);
 	}
 
-}
\ No newline at end of file
+}
diff --git a/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java b/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java
index 5e30dced..e5472857 100644
--- a/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java
+++ b/java-security-test/src/test/java/com/sap/cloud/security/test/JwtGeneratorTest.java
@@ -61,7 +61,9 @@ public class JwtGeneratorTest {
 		assertThat(token).isNotNull();
 		assertThat(token.getHeaderParameterAsString(TokenHeader.ALGORITHM)).isEqualTo(RS256.value());
 		assertThat(token.getClaimAsStringList(TokenClaims.AUDIENCE)).containsExactly(DEFAULT_CLIENT_ID);
-		assertThat(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).isEqualTo(DEFAULT_CLIENT_ID);
+		assertThat(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).isEqualTo(DEFAULT_CLIENT_ID); // deprecated
+		assertThat(token.getClaimAsString(TokenClaims.AUTHORIZATION_PARTY)).isEqualTo(DEFAULT_CLIENT_ID);
+		assertThat(token.getClientId()).isEqualTo(DEFAULT_CLIENT_ID);
 		assertThat(token.getExpiration()).isEqualTo(JwtGenerator.NO_EXPIRE_DATE);
 	}
 
@@ -81,7 +83,8 @@ public class JwtGeneratorTest {
 		assertThat(token).isNotNull();
 		assertThat(token.getHeaderParameterAsString(TokenHeader.KEY_ID)).isEqualTo(DEFAULT_KEY_ID_IAS);
 		assertThat(token.getClaimAsString(TokenClaims.AUDIENCE)).isEqualTo("T000310");
-		assertThat(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).isEqualTo("T000310");
+		assertThat(token.getClaimAsString(TokenClaims.AUTHORIZATION_PARTY)).isEqualTo("T000310");
+		assertThat(token.getClientId()).isEqualTo("T000310");
 		assertThat(token.getExpiration()).isEqualTo(JwtGenerator.NO_EXPIRE_DATE);
 		assertThat(token.getPrincipal().getName()).isEqualTo("1234567890");
 		String encodedModulusN = Base64.getUrlEncoder()
@@ -118,14 +121,29 @@ public class JwtGeneratorTest {
 	}
 
 	@Test
-	public void withClaimClientId_overwritesClaim() {
+	public void withClaimClientId_doesNotOverwriteClientId() {
 		String clientId = "myClientId";
 
 		Token token = cut
-				.withClaimValue(TokenClaims.XSUAA.CLIENT_ID, clientId)
+				.withClaimValue(TokenClaims.XSUAA.CLIENT_ID, clientId) // this has changed incompatible with version
+																		// 2.8.0!!!
 				.createToken();
 
-		assertThat(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).isEqualTo(clientId);
+		assertThat(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).isEqualTo(clientId); // for compatibility
+		assertThat(token.getClientId()).isEqualTo(DEFAULT_CLIENT_ID); // client id can only be overwritten by setting
+																		// AUTHORIZATION_PARTY
+	}
+
+	@Test
+	public void withClaimAzp_overwritesClientId() {
+		String clientId = "myClientId";
+
+		Token token = cut
+				.withClaimValue(TokenClaims.AUTHORIZATION_PARTY, clientId) // overwrite client id
+				.withClaimValue(TokenClaims.AUTHORIZATION_PARTY, clientId) // overwrites client id
+				.createToken();
+
+		assertThat(token.getClientId()).isEqualTo(clientId);
 	}
 
 	@Test
@@ -319,7 +337,8 @@ public class JwtGeneratorTest {
 		assertThat(token.getClaimAsString(TokenClaims.XSUAA.ZONE_ID)).isEqualTo("zone-id");
 		assertThat(token.getClaimAsStringList(TokenClaims.XSUAA.SCOPES)).containsExactlyInAnyOrder("openid",
 				"app1.scope");
-		assertThat(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).isEqualTo("testingClientId");
+		assertThat(token.getClientId()).isEqualTo("testingClientId");
+		assertThat(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).isEqualTo("cidTestingClientId");
 		assertThat(token.getClaimAsStringList(TokenClaims.AUDIENCE)).containsExactly("app1.scope");
 	}
 
diff --git a/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java b/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java
index 07b53119..ed45f8c3 100644
--- a/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java
+++ b/java-security-test/src/test/java/com/sap/cloud/security/test/SecurityTestRuleTest.java
@@ -84,8 +84,9 @@ public class SecurityTestRuleTest {
 				.withClaimValue(TokenClaims.ISSUER, "issuer")
 				.withLocalScopes("scope1")
 				.withHeaderParameter(TokenHeader.TYPE, "type").createToken();
-
 		assertThat(generatedToken.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID))
+				.isEqualTo(SecurityTestRule.DEFAULT_CLIENT_ID); // for compatibility reasons
+		assertThat(generatedToken.getClientId())
 				.isEqualTo(SecurityTestRule.DEFAULT_CLIENT_ID);
 		assertThat(generatedToken.getClaimAsStringList(TokenClaims.XSUAA.SCOPES))
 				.containsExactly(SecurityTestRule.DEFAULT_APP_ID + ".scope1");
diff --git a/java-security-test/src/test/resources/token.json b/java-security-test/src/test/resources/token.json
index ccab5ee7..7179edcd 100644
--- a/java-security-test/src/test/resources/token.json
+++ b/java-security-test/src/test/resources/token.json
@@ -4,7 +4,8 @@
     "kid": "kid-custom"
   },
   "payload": {
-    "cid": "testingClientId",
+    "cid": "cidTestingClientId",
+    "azp": "testingClientId",
     "zid": "zone-id",
     "exp": 1598314740,
     "scope": [
diff --git a/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java b/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java
index 80e8d05d..c6574d82 100644
--- a/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java
@@ -22,8 +22,7 @@ import java.io.IOException;
 
 import static com.sap.cloud.security.adapter.xs.XSUserInfoAdapter.*;
 import static com.sap.cloud.security.config.cf.CFConstants.XSUAA.IDENTITY_ZONE;
-import static com.sap.cloud.security.token.TokenClaims.XSUAA.TRUSTED_CLIENT_ID_SUFFIX;
-import static com.sap.cloud.security.token.TokenClaims.XSUAA.XS_USER_ATTRIBUTES;
+import static com.sap.cloud.security.token.TokenClaims.XSUAA.*;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -123,7 +122,7 @@ public class XSUserInfoAdapterTest {
 
 	@Test
 	public void testGetJsonValue() throws XSUserInfoException {
-		assertThat(cut.getJsonValue("cid")).isEqualTo("sb-clone1!b5|LR-master!b5");
+		assertThat(cut.getJsonValue("azp")).isEqualTo("sb-clone1!b5|LR-master!b5");
 	}
 
 	@Test
@@ -380,6 +379,8 @@ public class XSUserInfoAdapterTest {
 
 		when(token.getTokenValue()).thenReturn(mockTokenValue);
 		when(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).thenReturn("sb-margin-assurance-ui!i1");
+		when(token.hasClaim(CLIENT_ID)).thenReturn(true);
+		when(token.getClientId()).thenCallRealMethod();
 		when(token.getClaimAsString(TokenClaims.XSUAA.ZONE_ID)).thenReturn("uaa");
 		when(token.getGrantType()).thenReturn(GrantType.AUTHORIZATION_CODE);
 
@@ -402,10 +403,10 @@ public class XSUserInfoAdapterTest {
 
 	@Test
 	public void isForeignModeFalse_whenTrustedClientIdSuffixMatches() throws XSUserInfoException {
-		String tokenClientId = "sb-clone1!b22|brokerplanmasterapp!b123"; // cid
+		String tokenClientId = "sb-clone1!b22|brokerplanmasterapp!b123"; // azp
 		String configurationAppId = "brokerplanmasterapp!b123";
 		XsuaaToken token = mock(XsuaaToken.class);
-		when(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).thenReturn(tokenClientId);
+		when(token.getClientId()).thenReturn(tokenClientId);
 		when(token.getClaimAsString(TokenClaims.XSUAA.ZONE_ID)).thenReturn("otherIdentityZone");
 
 		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
@@ -424,7 +425,7 @@ public class XSUserInfoAdapterTest {
 	@Test
 	public void isForeignModeFalse_WhenIdentityZoneDoesNotMatchButCliendIdIsApplicationPlan()
 			throws XSUserInfoException {
-		String tokenClientId = "sb-application!t0123"; // cid
+		String tokenClientId = "sb-application!t0123"; // azp
 		String identityZone = "brokerplanmasterapp!b123"; // ext_attr -> zdn
 		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
 				.withClientId(tokenClientId)
@@ -441,7 +442,7 @@ public class XSUserInfoAdapterTest {
 	@Test
 	public void isForeignModeFalse_WhenIdentityZoneDoesNotMatchButCliendIdIsBrokerPlan()
 			throws XSUserInfoException {
-		String tokenClientId = "sb-application!b0123"; // cid
+		String tokenClientId = "sb-application!b0123"; // azp
 		String identityZone = "brokerplanmasterapp!b123"; // ext_attr -> zdn
 		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
 				.withClientId(tokenClientId)
@@ -457,7 +458,7 @@ public class XSUserInfoAdapterTest {
 
 	@Test
 	public void isForeignModeFalse_WhenClientIdAndIdentityZonesMatch() throws XSUserInfoException {
-		String tokenClientId = "sb-application"; // cid
+		String tokenClientId = "sb-application"; // azp
 		String identityZone = "brokerplanmasterapp!b123"; // ext_attr -> zdn
 		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
 				.withClientId(tokenClientId)
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java b/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java
index ebdc44fd..917fe88a 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/AbstractTokenTest.java
@@ -4,14 +4,24 @@ import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.json.JsonObject;
 import com.sap.cloud.security.json.JsonParsingException;
 import org.apache.commons.io.IOUtils;
-import org.junit.Test;
+import org.assertj.core.util.Sets;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.Mockito;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.security.Principal;
 import java.time.Instant;
 import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Stream;
 
+import static com.sap.cloud.security.token.TokenClaims.AUTHORIZATION_PARTY;
+import static com.sap.cloud.security.token.TokenClaims.XSUAA.CLIENT_ID;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.Mockito.when;
@@ -135,8 +145,55 @@ public class AbstractTokenTest {
 				.contains(cut.getHeaderParameterAsString(TokenHeader.JWKS_URL))
 				.contains(cut.getHeaderParameterAsString(TokenHeader.KEY_ID))
 				.contains(cut.getAudiences())
-				.contains(cut.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID))
+				.contains(cut.getClientId())
 				.contains(cut.getClaimAsString(TokenClaims.XSUAA.GRANT_TYPE))
 				.contains(cut.getClaimAsStringList(TokenClaims.XSUAA.SCOPES));
 	}
+
+	@ParameterizedTest
+	@ValueSource(strings = { "cid", "", "    " })
+	public void getClientIdWithCidTest(String cid) throws InvalidTokenException {
+		AbstractToken token = Mockito.mock(AbstractToken.class);
+		when(token.getAudiences()).thenReturn(Collections.emptySet());
+		when(token.getClaimAsString(AUTHORIZATION_PARTY)).thenReturn(null);
+		when(token.getClaimAsString(CLIENT_ID)).thenReturn(cid);
+		when(token.hasClaim(CLIENT_ID)).thenReturn(!cid.trim().isEmpty());
+		when(token.getClientId()).thenCallRealMethod();
+		try {
+			assertThat(token.getClientId()).isEqualTo(cid);
+		} catch (InvalidTokenException e) {
+			assertThatThrownBy(() -> token.getClientId()).isExactlyInstanceOf(InvalidTokenException.class);
+		}
+	}
+
+	@ParameterizedTest
+	@MethodSource("clientIdTestArguments")
+	public void getClientIdTest(String azp, Set<String> aud, String expectedClientId,
+			Class<InvalidTokenException> expectedException) throws InvalidTokenException {
+		AbstractToken token = Mockito.mock(AbstractToken.class);
+		when(token.getAudiences()).thenReturn(aud);
+		when(token.getClaimAsString(AUTHORIZATION_PARTY)).thenReturn(azp);
+		when(token.getClientId()).thenCallRealMethod();
+
+		if (expectedException != null) {
+			assertThatThrownBy(() -> token.getClientId()).isExactlyInstanceOf(expectedException);
+		} else {
+			assertThat(token.getClientId()).isEqualTo(expectedClientId);
+		}
+	}
+
+	private static Stream<Arguments> clientIdTestArguments() {
+		return Stream.of(
+				Arguments.of("azp", Sets.newLinkedHashSet("aud1", "aud2"), "azp", null),
+				Arguments.of("azp", Sets.newLinkedHashSet("aud"), "azp", null),
+				Arguments.of("", Sets.newLinkedHashSet("aud1", "aud2"), null, InvalidTokenException.class),
+				Arguments.of("", Sets.newLinkedHashSet("aud"), "aud", null),
+				Arguments.of("", Sets.newLinkedHashSet(), null, InvalidTokenException.class),
+				Arguments.of(null, Sets.newLinkedHashSet("aud"), "aud", null),
+				Arguments.of(null, Sets.newLinkedHashSet("aud1", "aud2"), null, InvalidTokenException.class),
+				Arguments.of(null, Sets.newLinkedHashSet(), null, InvalidTokenException.class),
+				Arguments.of("   ", Sets.newLinkedHashSet("aud"), "aud", null),
+				Arguments.of("   ", Sets.newLinkedHashSet("aud1", "aud2"), null, InvalidTokenException.class),
+				Arguments.of("   ", Sets.newLinkedHashSet(), null, InvalidTokenException.class));
+	}
 }
\ No newline at end of file
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java b/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java
index d6467fbb..54b3fd7a 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/XsuaaTokenTest.java
@@ -70,7 +70,7 @@ public class XsuaaTokenTest {
 
 	@Test
 	public void getClientPrincipal() {
-		assertThat(clientCredentialsToken.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).isEqualTo("sap_osb");
+		assertThat(clientCredentialsToken.getClientId()).isEqualTo("sap_osb");
 		assertThat(clientCredentialsToken.getPrincipal()).isNotNull();
 		assertThat(clientCredentialsToken.getPrincipal().getName()).isEqualTo("client/sap_osb");
 	}
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidatorTest.java
index cd7f1e48..97360662 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidatorTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtAudienceValidatorTest.java
@@ -26,8 +26,8 @@ public class JwtAudienceValidatorTest {
 		Mockito.when(token.getAudiences()).thenReturn(
 				Sets.newLinkedHashSet("client", "foreignclient", "sb-test4!t1.data"));
 		Mockito.when(token.getService()).thenReturn(Service.XSUAA);
-		Mockito.when(token.hasClaim(TokenClaims.XSUAA.CLIENT_ID)).thenReturn(true);
-		Mockito.when(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID)).thenReturn("client");
+		Mockito.when(token.hasClaim(TokenClaims.AUTHORIZATION_PARTY)).thenReturn(true);
+		Mockito.when(token.getClientId()).thenReturn("client");
 	}
 
 	@Test
@@ -99,9 +99,8 @@ public class JwtAudienceValidatorTest {
 	@Test
 	public void validate_tokenClientIdMatchesTrustedBrokerClientId() {
 		Mockito.when(token.getAudiences()).thenReturn(Collections.emptySet());
-		Mockito.when(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID))
+		Mockito.when(token.getClientId())
 				.thenReturn("sb-clone-app-id!b123|" + XSUAA_BROKER_XSAPPNAME);
-
 		// configures audience validator with client-id from VCAP_SERVICES
 		ValidationResult result = new JwtAudienceValidator(XSUAA_BROKER_XSAPPNAME)
 				.validate(token);
@@ -112,7 +111,7 @@ public class JwtAudienceValidatorTest {
 	@Test
 	public void validate_tokenClientIdDoesNotMatchTrustedBrokerClientId() {
 		Mockito.when(token.getAudiences()).thenReturn(Collections.emptySet());
-		Mockito.when(token.getClaimAsString(TokenClaims.XSUAA.CLIENT_ID))
+		Mockito.when(token.getClientId())
 				.thenReturn("sb-clone-app-id!b123|xxx" + XSUAA_BROKER_XSAPPNAME);
 
 		// configures audience validator with client-id from VCAP_SERVICES
diff --git a/spring-xsuaa-test/src/main/java/com/sap/cloud/security/xsuaa/test/JwtGenerator.java b/spring-xsuaa-test/src/main/java/com/sap/cloud/security/xsuaa/test/JwtGenerator.java
index cceba058..7c61a544 100644
--- a/spring-xsuaa-test/src/main/java/com/sap/cloud/security/xsuaa/test/JwtGenerator.java
+++ b/spring-xsuaa-test/src/main/java/com/sap/cloud/security/xsuaa/test/JwtGenerator.java
@@ -36,7 +36,8 @@ public class JwtGenerator {
 
 		static final String CLAIM_XS_USER_ATTRIBUTES = "xs.user.attributes";
 		static final String CLAIM_SCOPES = "scope";
-		static final String CLAIM_CLIENT_ID = "cid";
+		static final String CLAIM_CLIENT_ID = "cid"; // Client Id left for backward compatibility
+		static final String CLAIM_AUTHORIZATION_PARTY = "azp";
 		static final String CLAIM_USER_NAME = "user_name";
 		static final String CLAIM_EMAIL = "email";
 		static final String CLAIM_ORIGIN = "origin";
@@ -66,7 +67,7 @@ public class JwtGenerator {
 	// see XsuaaToken.GRANTTYPE_SAML2BEARER
 	private static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:saml2-bearer";
 
-	private final String clientId;
+	private final String azp;
 	private final String identityZoneId;
 	private final String subdomain;
 
@@ -80,11 +81,11 @@ public class JwtGenerator {
 	private boolean deriveAudiences = false;
 
 	/**
-	 * Specifies clientId of the JWT token claim.
+	 * Specifies authorization party of the JWT token claim.
 	 *
 	 * @param clientId
 	 *            the XSUAA client id, e.g. sb-applicationName!t123, defines the
-	 *            value of the JWT token claims "client_id" and "cid". A token is
+	 *            value of the JWT token claims "azp" and "cid". A token is
 	 *            considered to be valid when it matches the "xsuaa.clientid" xsuaa
 	 *            service configuration (VCAP_SERVICES).
 	 */
@@ -93,11 +94,11 @@ public class JwtGenerator {
 	}
 
 	/**
-	 * Specifies clientId of the JWT token claim.
+	 * Specifies authorization party of the JWT token claim.
 	 *
 	 * @param clientId
 	 *            the XSUAA client id, e.g. sb-applicationName!t123, defines the
-	 *            value of the JWT token claims "client_id" and "cid". A token is
+	 *            value of the JWT token claims "azp" and "cid". A token is
 	 *            considered to be valid when it matches the "xsuaa.clientid" xsuaa
 	 *            service configuration (VCAP_SERVICES).
 	 * @param port
@@ -113,7 +114,7 @@ public class JwtGenerator {
 	 *
 	 * @param clientId
 	 *            the XSUAA client id, e.g. sb-applicationName!t123, defines the
-	 *            value of the JWT token claims "client_id" and "cid". A token is
+	 *            value of the JWT token claims "azp" and "cid". A token is
 	 *            considered to be valid when it matches the "xsuaa.clientid" xsuaa
 	 *            service configuration (VCAP_SERVICES).
 	 * @param subdomain
@@ -126,7 +127,7 @@ public class JwtGenerator {
 	}
 
 	public JwtGenerator(String clientId, String subdomain, String identityZoneId) {
-		this.clientId = clientId;
+		this.azp = clientId;
 		this.subdomain = subdomain;
 		this.identityZoneId = identityZoneId;
 		this.port = MOCK_XSUAA_DEFAULT_PORT;
@@ -261,7 +262,7 @@ public class JwtGenerator {
 	}
 
 	/**
-	 * Builds a basic Jwt with the given clientId, userName, scopes, user attributes
+	 * Builds a basic Jwt with the given azp, userName, scopes, user attributes
 	 * claims and the keyId header.
 	 *
 	 * @return jwt
@@ -313,8 +314,7 @@ public class JwtGenerator {
 	 * This replaces these placeholders:
 	 * <ul>
 	 * <li>"$exp" with a date, that will not expire</li>
-	 * <li>"$clientid" with the configured client id
-	 * {@link #JwtGenerator(String)}</li>
+	 * <li>"$azp" with the configured client id {@link #JwtGenerator(String)}</li>
 	 * <li>"$zdn" with the configured subdomain
 	 * {@link #JwtGenerator(String, String)}</li>
 	 * <li>"$zid" with "uaa" or with the configured subdomain
@@ -382,7 +382,8 @@ public class JwtGenerator {
 		return new JWTClaimsSet.Builder()
 				.issueTime(new Date())
 				.expirationTime(JwtGenerator.NO_EXPIRE_DATE)
-				.claim(TokenClaims.CLAIM_CLIENT_ID, clientId)
+				.claim(TokenClaims.CLAIM_CLIENT_ID, azp) // Client Id left for backward compatibility
+				.claim(TokenClaims.CLAIM_AUTHORIZATION_PARTY, azp)
 				.claim(TokenClaims.CLAIM_ORIGIN, "userIdp")
 				.claim(TokenClaims.CLAIM_USER_NAME, userName)
 				.claim(TokenClaims.CLAIM_EMAIL, userName + "@test.org")
@@ -419,7 +420,8 @@ public class JwtGenerator {
 
 	private String replacePlaceholders(String claims) {
 		claims = claims.replace("$exp", String.valueOf(NO_EXPIRE));
-		claims = claims.replace("$clientid", clientId);
+		claims = claims.replace("$clientid", azp); // Client Id left for backward compatibility
+		claims = claims.replace("$azp", azp);
 		claims = claims.replace("$zdn", subdomain);
 		claims = claims.replace("$zid", identityZoneId);
 		claims = claims.replace("$username", userName);
diff --git a/spring-xsuaa-test/src/test/java/com/sap/cloud/security/xsuaa/test/JwtGeneratorTest.java b/spring-xsuaa-test/src/test/java/com/sap/cloud/security/xsuaa/test/JwtGeneratorTest.java
index 4ba9fb6e..f5861bab 100644
--- a/spring-xsuaa-test/src/test/java/com/sap/cloud/security/xsuaa/test/JwtGeneratorTest.java
+++ b/spring-xsuaa-test/src/test/java/com/sap/cloud/security/xsuaa/test/JwtGeneratorTest.java
@@ -49,6 +49,8 @@ public class JwtGeneratorTest {
 		jwtGenerator.setUserName(MY_USER_NAME);
 		Jwt jwt = jwtGenerator.getToken();
 		assertThat(jwt.getClaimAsString(JwtGenerator.TokenClaims.CLAIM_CLIENT_ID), equalTo(MY_CLIENT_ID));
+		assertThat(jwt.getClaimAsString(JwtGenerator.TokenClaims.CLAIM_AUTHORIZATION_PARTY), equalTo(
+				MY_CLIENT_ID));
 		assertThat(jwt.getClaimAsString(JwtGenerator.TokenClaims.CLAIM_ZONE_ID), startsWith(MY_SUBDOMAIN));
 		assertThat(jwt.getClaimAsString(JwtGenerator.TokenClaims.CLAIM_USER_NAME), equalTo(MY_USER_NAME));
 		assertThat(jwt.getClaimAsString(JwtGenerator.TokenClaims.CLAIM_EMAIL), startsWith(MY_USER_NAME));
@@ -153,12 +155,13 @@ public class JwtGeneratorTest {
 
 	@Test
 	public void testBasicJwtTokenWithIdentityZoneId() {
-		JwtGenerator jwtGenerator = new JwtGenerator("clientId", "subdomain", "tenantId");
+		JwtGenerator jwtGenerator = new JwtGenerator("azp", "subdomain", "tenantId");
 		Jwt jwt = jwtGenerator.getToken();
 
 		assertThat(jwt.getHeaders(), hasEntry(TokenHeaders.JKU, "http://localhost:33195/subdomain/token_keys"));
 		assertThat(jwt.getHeaders(), hasEntry(TokenHeaders.KID, "legacy-token-key"));
-		assertThat(jwt.getClaims(), hasEntry(TokenClaims.CLAIM_CLIENT_ID, "clientId"));
+		assertThat(jwt.getClaims(), hasEntry(TokenClaims.CLAIM_CLIENT_ID, "azp"));
+		assertThat(jwt.getClaims(), hasEntry(TokenClaims.CLAIM_AUTHORIZATION_PARTY, "azp"));
 		assertThat(jwt.getClaims(), hasEntry(TokenClaims.CLAIM_ZDN, "subdomain"));
 		assertThat(jwt.getClaims(), hasEntry(TokenClaims.CLAIM_ZONE_ID, "tenantId"));
 	}
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/XsuaaTokenTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/XsuaaTokenTest.java
index 82f18c9f..2245a973 100644
--- a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/XsuaaTokenTest.java
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/XsuaaTokenTest.java
@@ -1,24 +1,26 @@
 package com.sap.cloud.security.xsuaa.token;
 
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.hamcrest.CoreMatchers.hasItem;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.CoreMatchers.startsWith;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.IOException;
 import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import java.util.*;
+import java.util.stream.Stream;
+
+import com.sap.cloud.security.token.InvalidTokenException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -39,15 +41,17 @@ public class XsuaaTokenTest {
 	private String scopeOther = "other-app-name!t777.Other";
 	private String userName = "testUser";
 	private String zoneId = "e2f7fbdb-0326-40e6-940f-dfddad057ff3";
+	private static final String CLIENT_ID = "sb-java-hello-world";
 
-	@Before
+	@BeforeEach
 	public void setup() throws IOException {
 		claimsSetBuilder = new JWTClaimsSet.Builder()
 				.issueTime(new Date())
 				.expirationTime(JwtGenerator.NO_EXPIRE_DATE)
 				.claim(TokenClaims.CLAIM_USER_NAME, userName).claim(TokenClaims.CLAIM_EMAIL, userName + "@test.org")
-				.claim(TokenClaims.CLAIM_ZONE_ID, zoneId).claim(TokenClaims.CLAIM_CLIENT_ID, "sb-java-hello-world")
+				.claim(TokenClaims.CLAIM_ZONE_ID, zoneId).claim(TokenClaims.CLAIM_CLIENT_ID, CLIENT_ID)
 				.claim(TokenClaims.CLAIM_ORIGIN, "userIdp")
+				.claim(TokenClaims.CLAIM_AUTHORIZATION_PARTY, "client")
 				.claim(TokenClaims.CLAIM_GRANT_TYPE, XsuaaToken.GRANTTYPE_SAML2BEARER);
 
 		jwtSaml = new JwtGenerator().createFromTemplate("/saml.txt");
@@ -59,7 +63,7 @@ public class XsuaaTokenTest {
 		token = createToken(claimsSetBuilder);
 
 		assertThat(token.getPassword(), nullValue());
-		assertThat(token.getClientId(), is("sb-java-hello-world"));
+		assertThat(token.getClientId(), is("client"));
 		assertThat(token.getGrantType(), is(XsuaaToken.GRANTTYPE_SAML2BEARER));
 		assertThat(token.getOrigin(), is("userIdp"));
 		assertThat(token.getLogonName(), is(userName));
@@ -77,6 +81,37 @@ public class XsuaaTokenTest {
 		assertThat(token.getAdditionalAuthAttribute("any"), nullValue());
 	}
 
+	@ParameterizedTest
+	@ValueSource(strings = { "", "  " })
+	public void getClientIdNegativeTest(String cid) {
+		token = createToken(
+				claimsSetBuilder.claim(TokenClaims.CLAIM_AUTHORIZATION_PARTY, null).audience(Collections.EMPTY_LIST)
+						.claim(TokenClaims.CLAIM_CLIENT_ID, cid));
+		assertThatThrownBy(() -> token.getClientId()).isExactlyInstanceOf(InvalidTokenException.class);
+	}
+
+	@ParameterizedTest
+	@MethodSource("clientIdTestArguments")
+	public void getClientIdTest(String azp, List<String> aud, String expectedClientId) {
+		token = createToken(claimsSetBuilder.claim(TokenClaims.CLAIM_AUTHORIZATION_PARTY, azp).audience(aud));
+		assertThat(token.getClientId(), is(expectedClientId));
+	}
+
+	private static Stream<Arguments> clientIdTestArguments() {
+		return Stream.of(
+				Arguments.of("azp", Arrays.asList("aud1", "aud2"), "azp"),
+				Arguments.of("azp", Arrays.asList("aud"), "azp"),
+				Arguments.of("", Arrays.asList("aud1", "aud2"), CLIENT_ID),
+				Arguments.of("", Arrays.asList("aud"), "aud"),
+				Arguments.of("", Arrays.asList(), CLIENT_ID),
+				Arguments.of(null, Arrays.asList("aud"), "aud"),
+				Arguments.of(null, Arrays.asList("aud1", "aud2"), CLIENT_ID),
+				Arguments.of(null, Arrays.asList(), CLIENT_ID),
+				Arguments.of("   ", Arrays.asList("aud"), "aud"),
+				Arguments.of("   ", Arrays.asList("aud1", "aud2"), CLIENT_ID),
+				Arguments.of("   ", Arrays.asList(), CLIENT_ID));
+	}
+
 	@Test
 	public void authenticationConverterShouldSetAuthorities() {
 		TokenAuthenticationConverter converter = new TokenAuthenticationConverter(xsAppName);
@@ -143,7 +178,7 @@ public class XsuaaTokenTest {
 	@Test
 	public void getSubaccountIdFromSystemAttributes() throws IOException {
 		Token token = new XsuaaToken(jwtSaml);
-		Assert.assertEquals("test-subaccount", token.getSubaccountId());
+		assertThat(token.getSubaccountId(), is("test-subaccount"));
 	}
 
 	@Test
@@ -203,14 +238,14 @@ public class XsuaaTokenTest {
 	public void getPrincipalNameReturnUniqueLogonNameWithOrigin() {
 		Token token = new XsuaaToken(jwtSaml);
 		UserDetails principal = token;
-		Assert.assertEquals("user/useridp/Mustermann", principal.getUsername());
+		assertEquals("user/useridp/Mustermann", principal.getUsername());
 	}
 
 	@Test
 	public void getPrincipalNameReturnUniqueClientId() {
 		Token token = new XsuaaToken(jwtCC);
-		Assert.assertEquals("sb-java-hello-world", token.getClientId());
-		Assert.assertEquals("client/sb-java-hello-world", token.getUsername());
+		assertEquals(CLIENT_ID, token.getClientId());
+		assertEquals("client/sb-java-hello-world", token.getUsername());
 	}
 
 	@Test
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderTest.java
index 18f30786..4ed8381a 100644
--- a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderTest.java
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderTest.java
@@ -62,11 +62,13 @@ public class XsuaaJwtDecoderTest {
 		assertThatThrownBy(() -> cut.decode(token)).isInstanceOf(JwtException.class)
 				.hasMessageContaining("JWT verification failed: Do not trust 'jku' token header");
 
-		cut.setTokenInfoExtractor(new TokenInfoExtractorImpl("http://myauth.ondemand.com@malicious.ondemand.com/token_keys"));
+		cut.setTokenInfoExtractor(
+				new TokenInfoExtractorImpl("http://myauth.ondemand.com@malicious.ondemand.com/token_keys"));
 		assertThatThrownBy(() -> cut.decode(token)).isInstanceOf(JwtException.class)
 				.hasMessageContaining("JWT verification failed: Do not trust 'jku' token header");
 
-		cut.setTokenInfoExtractor(new TokenInfoExtractorImpl("http://malicious.ondemand.com/token_keys///myauth.ondemand.com/token_keys"));
+		cut.setTokenInfoExtractor(new TokenInfoExtractorImpl(
+				"http://malicious.ondemand.com/token_keys///myauth.ondemand.com/token_keys"));
 		assertThatThrownBy(() -> cut.decode(token)).isInstanceOf(JwtException.class)
 				.hasMessageContaining("JWT verification failed: Do not trust 'jku' token header");
 	}