Skip to content

Commit

Permalink
Native auth: Add support for claims request, AB#3117218, Fixes AB#311…
Browse files Browse the repository at this point in the history
…7218 (#2572)

Adding support on native authentication for claims request. During
sign-in, native authentication users can now specify claims requests,
which will be sent to the /token endpoint. This enhancement specifically
supports authentication context.

MSAL PR:
AzureAD/microsoft-authentication-library-for-android#2246


[AB#3117218](https://identitydivision.visualstudio.com/Engineering/_workitems/edit/3117218)
  • Loading branch information
nilo-ms authored Jan 23, 2025
1 parent bb5aaaa commit 223ac0c
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 11 deletions.
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
vNext
----------
- [MINOR] Add support for OneBox Environment (#2559)
- [MINOR] Add support for claims requests for native authentication (#2572)

Version 19.0.0
----------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ class NativeAuthMsalController : BaseNativeAuthController() {
"Parameters has password"
)
val mergedScopes = addDefaultScopes(parameters.scopes)
var parametersWithScopes = CommandUtil.createSignInStartCommandParametersWithScopes(
val parametersWithScopes = CommandUtil.createSignInStartCommandParametersWithScopes(
parameters,
mergedScopes
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ public static SignInSubmitPasswordCommandParameters createSignInSubmitPasswordCo
.password(parameters.getPassword())
.scopes(parameters.getScopes())
.correlationId(correlationId)
.claimsRequestJson(parameters.getClaimsRequestJson())
.challengeType(parameters.getChallengeType())
.build();

Expand Down Expand Up @@ -189,6 +190,7 @@ public static SignInSubmitCodeCommandParameters createSignInSubmitCodeCommandPar
.scopes(parameters.getScopes())
.correlationId(parameters.getCorrelationId())
.challengeType(parameters.getChallengeType())
.claimsRequestJson(parameters.claimsRequestJson)
.build();

return commandParameters;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.microsoft.identity.common.nativeauth.internal.util

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.microsoft.identity.common.components.AndroidPlatformComponentsFactory
import com.microsoft.identity.common.java.interfaces.IPlatformComponents
import com.microsoft.identity.common.java.nativeauth.commands.parameters.MFASubmitChallengeCommandParameters
import com.microsoft.identity.common.java.nativeauth.commands.parameters.SignInStartCommandParameters
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
import org.robolectric.RobolectricTestRunner
import java.util.UUID

/**
* Tests for [CommandUtil].
*/
@RunWith(RobolectricTestRunner::class)
class CommandUtilTest {

private lateinit var platformComponents: IPlatformComponents
private lateinit var context: Context

@Before
fun setup() {
MockitoAnnotations.initMocks(this)
context = ApplicationProvider.getApplicationContext()
platformComponents = AndroidPlatformComponentsFactory.createFromContext(
context
)
}

@Test
fun testCreateSignInSubmitPasswordCommandParameters_containsCorrectInfo() {
val correlationId = UUID.randomUUID().toString()
val continuationToken = "continuation"
val signInStartParams = SignInStartCommandParameters.builder()
.password("test".toCharArray())
.claimsRequestJson("claimsRequestJson")
.clientId("clientId")
.challengeType(arrayListOf("OOB"))
.redirectUri("redirectUri")
.username("username")
.platformComponents(platformComponents)
.build()
val submitPasswordParams = CommandUtil.createSignInSubmitPasswordCommandParameters(signInStartParams, correlationId, continuationToken)

assert(submitPasswordParams.getPassword().contentEquals(signInStartParams.getPassword()))
assert(submitPasswordParams.getContinuationToken().contentEquals(continuationToken))
assert(submitPasswordParams.correlationId?.contentEquals(correlationId) == true)
assert(submitPasswordParams.getClaimsRequestJson().contentEquals(signInStartParams.getClaimsRequestJson()))
assert(submitPasswordParams.clientId?.contentEquals(signInStartParams.clientId) == true)
assert(submitPasswordParams.getChallengeType()?.equals(signInStartParams.getChallengeType()) == true)
assert(submitPasswordParams.redirectUri?.equals(signInStartParams.redirectUri) == true)
}

@Test
fun testCreateSignInSubmitCodeCommandParameters_containsCorrectInfo() {
val mfaSubmitChallengeParams = MFASubmitChallengeCommandParameters.builder()
.claimsRequestJson("claimsRequestJson")
.clientId("clientId")
.challengeType(arrayListOf("OOB"))
.redirectUri("redirectUri")
.platformComponents(platformComponents)
.challenge("123456")
.continuationToken("continuationToken")
.correlationId(UUID.randomUUID().toString())
.build()
val submitCodeParams = CommandUtil.createSignInSubmitCodeCommandParameters(mfaSubmitChallengeParams)

assert(submitCodeParams.getContinuationToken().contentEquals(mfaSubmitChallengeParams.getContinuationToken()))
assert(submitCodeParams.correlationId?.contentEquals(mfaSubmitChallengeParams.correlationId) == true)
assert(submitCodeParams.getClaimsRequestJson().contentEquals(mfaSubmitChallengeParams.getClaimsRequestJson()))
assert(submitCodeParams.clientId?.contentEquals(mfaSubmitChallengeParams.clientId) == true)
assert(submitCodeParams.getChallengeType()?.equals(mfaSubmitChallengeParams.getChallengeType()) == true)
assert(submitCodeParams.getCode() == mfaSubmitChallengeParams.getChallenge())
assert(submitCodeParams.redirectUri?.equals(mfaSubmitChallengeParams.redirectUri) == true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import lombok.experimental.SuperBuilder;

/**
* BaseSignInTokenCommandParameters is the base class for parameters for all all Native Auth sign in related commands.
* BaseSignInTokenCommandParameters is the base class for parameters for all Native Auth sign in related commands.
*/
@Getter
@EqualsAndHashCode(callSuper = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
//THE SOFTWARE.
package com.microsoft.identity.common.java.nativeauth.commands.parameters;

import javax.annotation.Nullable;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.EqualsAndHashCode;
import lombok.Getter;
Expand All @@ -45,6 +47,12 @@ public class MFADefaultChallengeCommandParameters extends BaseSignInTokenCommand
@NonNull
public final String continuationToken;

/**
* Claims to send to the token endpoint.
*/
@Nullable
public final String claimsRequestJson;

@NonNull
@Override
public String toUnsanitizedString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
// THE SOFTWARE.
package com.microsoft.identity.common.java.nativeauth.commands.parameters;

import javax.annotation.Nullable;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
Expand Down Expand Up @@ -49,6 +51,12 @@ public class MFASubmitChallengeCommandParameters extends BaseSignInTokenCommandP
@NonNull
public final String continuationToken;

/**
* Claims to send to the token endpoint.
*/
@Nullable
public final String claimsRequestJson;

@NonNull
@Override
public String toUnsanitizedString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ public class SignInStartCommandParameters extends BaseSignInTokenCommandParamete
@Nullable
public final char[] password;

/**
* Claims to send to the token endpoint.
*/
@Nullable
public final String claimsRequestJson;

@NonNull
@Override
public String toUnsanitizedString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
// THE SOFTWARE.
package com.microsoft.identity.common.java.nativeauth.commands.parameters;

import javax.annotation.Nullable;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
Expand Down Expand Up @@ -49,6 +51,12 @@ public class SignInSubmitCodeCommandParameters extends BaseSignInTokenCommandPar
@NonNull
public final String continuationToken;

/**
* Claims to send to the token endpoint.
*/
@Nullable
public final String claimsRequestJson;

@NonNull
@Override
public String toUnsanitizedString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
// THE SOFTWARE.
package com.microsoft.identity.common.java.nativeauth.commands.parameters;

import javax.annotation.Nullable;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.EqualsAndHashCode;
import lombok.Getter;
Expand Down Expand Up @@ -51,6 +53,12 @@ public class SignInSubmitPasswordCommandParameters extends BaseSignInTokenComman
@NonNull
public final String continuationToken;

/**
* Claims to send to the token endpoint.
*/
@Nullable
public final String claimsRequestJson;

@NonNull
@Override
public String toUnsanitizedString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ class NativeAuthRequestProvider(private val config: NativeAuthOAuth2Configuratio
clientId = config.clientId,
challengeType = config.challengeType,
requestUrl = signInTokenEndpoint,
headers = getRequestHeaders(commandParameters.getCorrelationId())
headers = getRequestHeaders(commandParameters.getCorrelationId()),
claimsRequestJson = commandParameters.claimsRequestJson
)
}

Expand Down Expand Up @@ -198,7 +199,8 @@ class NativeAuthRequestProvider(private val config: NativeAuthOAuth2Configuratio
clientId = config.clientId,
challengeType = config.challengeType,
requestUrl = signInTokenEndpoint,
headers = getRequestHeaders(commandParameters.getCorrelationId())
headers = getRequestHeaders(commandParameters.getCorrelationId()),
claimsRequestJson = commandParameters.claimsRequestJson
)
}
//endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ data class SignInTokenRequest private constructor(
scopes: List<String>? = null,
challengeType: String? = null,
requestUrl: String,
headers: Map<String, String?>
headers: Map<String, String?>,
claimsRequestJson: String?
): SignInTokenRequest {
// Check for empty Strings and empty Maps
ArgUtils.validateNonNullArg(oob, "oob")
Expand All @@ -74,7 +75,8 @@ data class SignInTokenRequest private constructor(
clientId = clientId,
grantType = NativeAuthConstants.GrantType.OOB,
challengeType = challengeType,
scope = scopes?.joinToString(" ")
scope = scopes?.joinToString(" "),
claimsRequestJson = claimsRequestJson
),
requestUrl = URL(requestUrl),
headers = headers,
Expand All @@ -96,7 +98,8 @@ data class SignInTokenRequest private constructor(
scopes: List<String>? = null,
challengeType: String? = null,
requestUrl: String,
headers: Map<String, String?>
headers: Map<String, String?>,
claimsRequestJson: String?
): SignInTokenRequest {
// Check for empty Strings and empty Maps
ArgUtils.validateNonNullArg(password, "password")
Expand All @@ -106,15 +109,15 @@ data class SignInTokenRequest private constructor(
ArgUtils.validateNonNullArg(requestUrl, "requestUrl")
ArgUtils.validateNonNullArg(headers, "headers")


return SignInTokenRequest(
parameters = NativeAuthRequestSignInTokenRequestParameters(
password = password,
continuationToken = continuationToken,
clientId = clientId,
grantType = NativeAuthConstants.GrantType.PASSWORD,
challengeType = challengeType,
scope = scopes?.joinToString(" ")
scope = scopes?.joinToString(" "),
claimsRequestJson = claimsRequestJson
),
requestUrl = URL(requestUrl),
headers = headers,
Expand Down Expand Up @@ -153,7 +156,8 @@ data class SignInTokenRequest private constructor(
username = username,
grantType = NativeAuthConstants.GrantType.CONTINUATION_TOKEN,
challengeType = challengeType,
scope = scopes?.joinToString(" ")
scope = scopes?.joinToString(" "),
claimsRequestJson = null
),
requestUrl = URL(requestUrl),
headers = headers
Expand All @@ -175,7 +179,8 @@ data class SignInTokenRequest private constructor(
@SerializedName("grant_type") val grantType: String,
@SerializedName("continuation_token") val continuationToken: String? = null,
@SerializedName("scope") val scope: String?,
@SerializedName("challenge_type") val challengeType: String?
@SerializedName("challenge_type") val challengeType: String?,
@SerializedName("claims") val claimsRequestJson: String?
) : NativeAuthRequestParameters() {
override fun toUnsanitizedString(): String = "NativeAuthRequestSignInTokenRequestParameters(nca=$nca, clientInfo=$clientInfo, clientId=$clientId, grantType=$grantType, scope=$scope, challengeType=$challengeType)"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import com.microsoft.identity.common.java.nativeauth.commands.parameters.SignUpS
import com.microsoft.identity.common.java.exception.ClientException
import com.microsoft.identity.common.java.interfaces.PlatformComponents
import com.microsoft.identity.common.java.nativeauth.providers.requests.NativeAuthRequest.Companion.toJsonString
import com.microsoft.identity.common.java.nativeauth.providers.requests.signin.SignInTokenRequest
import com.microsoft.identity.common.nativeauth.ApiConstants
import io.mockk.every
import io.mockk.mockk
Expand Down Expand Up @@ -778,6 +779,69 @@ class NativeAuthRequestProviderTest {
)
}

@Test
fun testSignInTokenOOBShouldContainsCorrectParams() {
val commandParameters = SignInSubmitCodeCommandParameters.builder()
.platformComponents(mock<PlatformComponents>())
.continuationToken(continuationToken)
.correlationId(correlationId)
.code("code")
.claimsRequestJson("claims")
.build()

val request = nativeAuthRequestProvider.createOOBTokenRequest(
commandParameters = commandParameters
)
assertEquals(request.parameters.oob, commandParameters.code)
assertEquals(request.parameters.claimsRequestJson, commandParameters.claimsRequestJson)
assertEquals(request.parameters.continuationToken, commandParameters.continuationToken)
assertEquals(request.parameters.challengeType, mockConfig.challengeType)
assertNull(request.parameters.scope)
}

@Test
fun testSignInTokenPasswordShouldContainsCorrectParams() {
val commandParameters = SignInSubmitPasswordCommandParameters.builder()
.platformComponents(mock<PlatformComponents>())
.continuationToken(continuationToken)
.correlationId(correlationId)
.password("pwd".toCharArray())
.claimsRequestJson("claims")
.build()

val request = nativeAuthRequestProvider.createPasswordTokenRequest(
commandParameters
)
assertEquals(request.parameters.password, commandParameters.password)
assertEquals(request.parameters.claimsRequestJson, commandParameters.claimsRequestJson)
assertEquals(request.parameters.continuationToken, commandParameters.continuationToken)
assertEquals(request.parameters.challengeType, mockConfig.challengeType)
assertNull(request.parameters.scope)
}

@Test
fun testSignInTokenContinuationShouldContainsCorrectParams() {
val scopes = arrayListOf("OOB", "PASSWORD")
val headers = mapOf("key" to "value")
val request = SignInTokenRequest.createContinuationTokenRequest(
continuationToken,
clientId,
username,
scopes,
challengeType,
ApiConstants.MockApi.signInTokenRequestUrl.toString(),
headers
)
assertNull(request.parameters.password)
assertNull(request.parameters.claimsRequestJson)
assertEquals(request.parameters.scope, "OOB PASSWORD")
assertEquals(request.parameters.continuationToken, continuationToken)
assertEquals(request.parameters.challengeType, challengeType)
assertEquals(request.parameters.username, username)
assertEquals(request.parameters.clientId, clientId)
assertEquals(request.headers, headers)
}

@Test(expected = ClientException::class)
fun testPasswordTokenRequestWithEmptyPasswordShouldThrowException() {
val commandParameters = SignInSubmitPasswordCommandParameters.builder()
Expand Down

0 comments on commit 223ac0c

Please sign in to comment.