From 07b9873446ff5cdc4c15ea971d490be8f8f324a0 Mon Sep 17 00:00:00 2001 From: Danilo Raspa <105228698+nilo-ms@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:19:53 +0000 Subject: [PATCH] Native auth: Add new E2E test for authentication context MFA, Fixes AB#3117220 (#2252) Add new E2E test to check that MFA is triggered when authentication context claim is specified during signIn. Test check also that returned access token contains authentication context claim (acrs). This test is currently disabled cause the email OTP service issue. [AB#3117220](https://identitydivision.visualstudio.com/Engineering/_workitems/edit/3117220) --- .../tests/network/nativeauth/SignInMFATest.kt | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInMFATest.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInMFATest.kt index bcfe24aea..ce6cb1015 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInMFATest.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInMFATest.kt @@ -23,11 +23,13 @@ package com.microsoft.identity.client.e2e.tests.network.nativeauth +import com.microsoft.identity.client.claims.ClaimsRequest import com.microsoft.identity.client.e2e.utils.assertResult import com.microsoft.identity.internal.testutils.nativeauth.ConfigType import com.microsoft.identity.internal.testutils.nativeauth.api.TemporaryEmailService import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication +import com.microsoft.identity.nativeauth.parameters.NativeAuthSignInParameters import com.microsoft.identity.nativeauth.statemachine.errors.MFASubmitChallengeError import com.microsoft.identity.nativeauth.statemachine.results.GetAccessTokenResult import com.microsoft.identity.nativeauth.statemachine.results.MFARequiredResult @@ -36,8 +38,10 @@ import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue +import org.junit.Assert.fail import org.junit.Ignore import org.junit.Test +import java.util.Base64 class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { @@ -258,4 +262,72 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { } } } + + /** + * Full flow: + * - SignIn specifying authentication context as claim + * - Receive MFA required error from API. + * - Request default challenge. + * - Submit correct challenge. + * - Complete MFA flow and complete sign in. + * - Check that access token contains authentication context claim. + * + */ + @Ignore("Retrieving OTP code failure and missing AC username") + @Test + fun `test MFA flow is triggered when authentication context is used as claim`() { + config = getConfig(ConfigType.SIGN_IN_MFA_SINGLE_AUTH) + application = setupPCA(config, defaultChallengeTypes) + resources = config.resources + val authenticationContextId = "c4" + val authenticationContextRequestClaimJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"$authenticationContextId\"}}}" + val authenticationContextATClaimJson = "\"acrs\":[\"$authenticationContextId\"]" + + retryOperation { + runBlocking { + val username = config.email + val password = getSafePassword() + val params = NativeAuthSignInParameters(username) + params.password = password.toCharArray() + params.claimsRequest = ClaimsRequest.getClaimsRequestFromJsonString(authenticationContextRequestClaimJson) + + val result = application.signIn(params) + assertResult(result) + + // Initiate challenge, send code to email + val sendChallengeResult = + (result as SignInResult.MFARequired).nextState.requestChallenge() + assertResult(sendChallengeResult) + (sendChallengeResult as MFARequiredResult.VerificationRequired) + assertNotNull(sendChallengeResult.sentTo) + assertNotNull(sendChallengeResult.codeLength) + assertNotNull(sendChallengeResult.channel) + + // Retrieve challenge from mailbox and submit + val otp = tempEmailApi.retrieveCodeFromInbox(username) + val submitCorrectChallengeResult = sendChallengeResult.nextState.submitChallenge(otp) + assertResult(submitCorrectChallengeResult) + + // Retrieve access token + val accountState = (submitCorrectChallengeResult as SignInResult.Complete).resultValue + val getAccessTokenResult = accountState.getAccessToken() + assertResult(getAccessTokenResult) + val authResult = (getAccessTokenResult as GetAccessTokenResult.Complete).resultValue + + // Check that AT contains authentication context claim + val atParts = authResult.accessToken.split(".") + if (atParts.size != 3) { + fail("Invalid Access token received") + return@runBlocking + } + val atBody = atParts[1] + val charset = charset("UTF-8") + val atDecoded = String( + Base64.getUrlDecoder().decode(atBody.toByteArray(charset)), + charset + ) + assertTrue(atDecoded.contains(authenticationContextATClaimJson)) + } + } + } }