diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml
index b1e16c7f..df0570e0 100644
--- a/msal4j-brokers/pom.xml
+++ b/msal4j-brokers/pom.xml
@@ -44,7 +44,7 @@
org.projectlombok
lombok
- 1.18.6
+ 1.18.36
provided
@@ -126,7 +126,14 @@
org.projectlombok
lombok-maven-plugin
- 1.18.2.0
+ 1.18.20.0
+
+
+ org.projectlombok
+ lombok
+ 1.18.36
+
+
diff --git a/msal4j-sdk/pom.xml b/msal4j-sdk/pom.xml
index 9dd0d24e..723c55d9 100644
--- a/msal4j-sdk/pom.xml
+++ b/msal4j-sdk/pom.xml
@@ -57,7 +57,7 @@
org.projectlombok
lombok
- 1.18.6
+ 1.18.36
provided
@@ -207,7 +207,14 @@
org.projectlombok
lombok-maven-plugin
- 1.18.2.0
+ 1.18.20.0
+
+
+ org.projectlombok
+ lombok
+ 1.18.36
+
+
@@ -323,7 +330,7 @@
biz.aQute.bnd
bnd-maven-plugin
- 4.3.1
+ 5.2.0
diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenCache.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenCache.java
index ebc1753c..2c39d67e 100644
--- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenCache.java
+++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenCache.java
@@ -477,7 +477,8 @@ private Optional getAccessTokenCacheEntity(
return accessTokens.values().stream().filter(
accessToken ->
- accessToken.homeAccountId.equals(account.homeAccountId()) &&
+ accessToken.homeAccountId != null &&
+ accessToken.homeAccountId.equals(account.homeAccountId()) &&
environmentAliases.contains(accessToken.environment) &&
accessToken.realm.equals(authority.tenant()) &&
accessToken.clientId.equals(clientId) &&
@@ -552,6 +553,7 @@ private Optional getRefreshTokenCacheEntity(
return refreshTokens.values().stream().filter(
refreshToken ->
+ refreshToken.homeAccountId != null &&
refreshToken.homeAccountId.equals(account.homeAccountId()) &&
environmentAliases.contains(refreshToken.environment) &&
refreshToken.clientId.equals(clientId)
diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java
index 815ebfcc..981b9b18 100644
--- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java
+++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java
@@ -16,6 +16,7 @@
import java.io.IOException;
import java.net.MalformedURLException;
+import java.nio.charset.StandardCharsets;
import java.util.*;
@Getter(AccessLevel.PACKAGE)
@@ -118,9 +119,15 @@ private AuthenticationResult createAuthenticationResultFromOauthHttpResponse(
}
AccountCacheEntity accountCacheEntity = null;
+ if (!StringHelper.isNullOrBlank(tokens.getIDTokenString())) {
+ String idTokenJson;
+ try {
+ idTokenJson = new String(Base64.getDecoder().decode(tokens.getIDTokenString().split("\\.")[1]), StandardCharsets.UTF_8);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new MsalServiceException("Error parsing ID token, missing payload section. Ensure that the ID token is following the JWT format.",
+ AuthenticationErrorCode.INVALID_JWT);
+ }
- if (tokens.getIDToken() != null) {
- String idTokenJson = tokens.getIDToken().getParsedParts()[1].decodeToString();
IdToken idToken = JsonHelper.convertJsonToObject(idTokenJson, IdToken.class);
AuthorityType type = msalRequest.application().authenticationAuthority.authorityType;
diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheTests.java
new file mode 100644
index 00000000..856b10d6
--- /dev/null
+++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheTests.java
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.aad.msal4j;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class CacheTests {
+
+ @Test
+ void cacheLookup_MixAccountBasedAndAssertionBasedSilentFlows() throws Exception {
+ DefaultHttpClient httpClientMock = mock(DefaultHttpClient.class);
+
+ ConfidentialClientApplication cca =
+ ConfidentialClientApplication.builder("clientId", ClientCredentialFactory.createFromSecret("password"))
+ .authority("https://login.microsoftonline.com/tenant/")
+ .instanceDiscovery(false)
+ .validateAuthority(false)
+ .httpClient(httpClientMock)
+ .build();
+
+ HashMap responseParameters = new HashMap<>();
+ //Acquire a token with no ID token/account associated with it
+ responseParameters.put("access_token", "accessTokenNoAccount");
+
+ ClientCredentialParameters clientCredentialParameters = ClientCredentialParameters.builder(Collections.singleton("someScopes")).build();
+ when(httpClientMock.send(any(HttpRequest.class))).thenReturn(TestHelper.expectedResponse(200, TestHelper.getSuccessfulTokenResponse(responseParameters)));
+ IAuthenticationResult resultNoAccount = cca.acquireToken(clientCredentialParameters).get();
+
+ //Ensure there is one token in the cache, and the result had no account
+ assertEquals(1, cca.tokenCache.accessTokens.size());
+ assertNull(resultNoAccount.account());
+ verify(httpClientMock, times(1)).send(any());
+
+ //Acquire a second token, this time with an ID token/account
+ responseParameters.put("access_token", "accessTokenWithAccount");
+ responseParameters.put("id_token", TestHelper.createIdToken(new HashMap<>()));
+
+ when(httpClientMock.send(any(HttpRequest.class))).thenReturn(TestHelper.expectedResponse(200, TestHelper.getSuccessfulTokenResponse(responseParameters)));
+ OnBehalfOfParameters onBehalfOfParametersarameters = OnBehalfOfParameters.builder(Collections.singleton("someOtherScopes"), new UserAssertion(TestHelper.signedAssertion)).build();
+ IAuthenticationResult resultWithAccount = cca.acquireToken(onBehalfOfParametersarameters).get();
+
+ //Ensure there are now two tokens in the cache, and the result has an account
+ assertEquals(2, cca.tokenCache.accessTokens.size());
+ assertNull(resultNoAccount.account());
+ verify(httpClientMock, times(2)).send(any());
+
+ //Make two silent calls, one with the account and one without
+ SilentParameters silentParametersNoAccount = SilentParameters.builder(Collections.singleton("someScopes")).build();
+ SilentParameters silentParametersWithAccount = SilentParameters.builder(Collections.singleton("someOtherScopes"), resultWithAccount.account()).build();
+
+ resultNoAccount = cca.acquireTokenSilently(silentParametersNoAccount).get();
+ resultWithAccount = cca.acquireTokenSilently(silentParametersWithAccount).get();
+
+ //Ensure the correct access tokens were returned from each silent call
+ assertEquals("accessTokenNoAccount", resultNoAccount.accessToken());
+ assertEquals("accessTokenWithAccount", resultWithAccount.accessToken());
+ }
+}
diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TestHelper.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TestHelper.java
index d1403bba..97d70ed1 100644
--- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TestHelper.java
+++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TestHelper.java
@@ -19,6 +19,7 @@
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -34,6 +35,17 @@ class TestHelper {
"\"expires_on\": %d ,\"expires_in\": %d," +
"\"token_type\":\"Bearer\"}";
+ static final String idTokenFormat = "{\"aud\": \"%s\"," +
+ "\"iss\": \"%s\"," +
+ "\"iat\": 1455833828," + "\"nbf\": 1455833828," + "\"exp\": 1455837728," +
+ "\"ipaddr\": \"131.107.159.117\"," +
+ "\"name\": \"%s\"," +
+ "\"oid\": \"%s\"," +
+ "\"preferred_username\": \"%s\"," +
+ "\"sub\": \"%s\"," +
+ "\"tid\": \"%s\"," +
+ "\"ver\": \"2.0\"}";
+
static X509Certificate x509Cert = getX509Cert();
static PrivateKey privateKey = getPrivateKey();
@@ -84,10 +96,10 @@ static String getSuccessfulTokenResponse(HashMap responseValues)
return String.format(successfulResponseFormat,
responseValues.getOrDefault("access_token", "access_token"),
- responseValues.getOrDefault("id_token", "id_token"),
+ responseValues.getOrDefault("id_token", ""),
responseValues.getOrDefault("refresh_token", "refresh_token"),
responseValues.getOrDefault("client_id", "client_id"),
- responseValues.getOrDefault("client_info", "client_info"),
+ responseValues.getOrDefault("client_info", "eyJ1aWQiOiI1OTdmODZjZC0xM2YzLTQ0YzAtYmVjZS1hMWU3N2JhNDMyMjgiLCJ1dGlkIjoiZjY0NWFkOTItZTM4ZC00ZDFhLWI1MTAtZDFiMDlhNzRhOGNhIn0"),
expiresOn,
expiresIn
);
@@ -106,6 +118,23 @@ static HttpResponse expectedResponse(int statusCode, String response) {
return httpResponse;
}
+ //Maps various values to the idTokenFormat string
+ static String createIdToken(HashMap idTokenValues) {
+ String tokenValues = String.format(idTokenFormat,
+ idTokenValues.getOrDefault("aud", "e854a4a7-6c34-449c-b237-fc7a28093d84"),
+ idTokenValues.getOrDefault("iss", "https://login.microsoftonline.com/6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e/v2.0/"),
+ idTokenValues.getOrDefault("name", "name"),
+ idTokenValues.getOrDefault("oid", "oid"),
+ idTokenValues.getOrDefault("preferred_username", "preferred_username"),
+ idTokenValues.getOrDefault("sub", "K4_SGGxKqW1SxUAmhg6C1F6VPiFzcx-Qd80ehIEdFus"),
+ idTokenValues.getOrDefault("client_info", "eyJ1aWQiOiI1OTdmODZjZC0xM2YzLTQ0YzAtYmVjZS1hMWU3N2JhNDMyMjgiLCJ1dGlkIjoiZjY0NWFkOTItZTM4ZC00ZDFhLWI1MTAtZDFiMDlhNzRhOGNhIn0")
+ );
+
+ String encodedTokenValues = Base64.getUrlEncoder().encodeToString(tokenValues.getBytes());
+
+ return String.format("someheader.%s.somesignature", encodedTokenValues);
+ }
+
static void setPrivateKeyAndCert() {
try {
CertAndKeyGen certGen = new CertAndKeyGen("RSA", "SHA256WithRSA", null);