@@ -191,7 +191,128 @@ final class MSALNativeAuthSignInWithMFAEndToEndTests: MSALNativeAuthEndToEndPass
191
191
// Now retrieve and submit the email OTP code
192
192
await completeSignInWithMFAFlow ( state: mfaRequiredState, username: username)
193
193
}
194
-
194
+
195
+ func test_signInAuthenticationContextClaim_mfaFlowIsTriggeredAndAccessTokenContainsClaims( ) async throws {
196
+ throw XCTSkip ( " Retrieving OTP failure " )
197
+ #if os(macOS)
198
+ throw XCTSkip ( " For some reason this test now requires Keychain access, reason needs to be investigated " )
199
+ #endif
200
+ guard let username = retrieveUsernameForSignInUsernamePasswordAndMFA ( ) ,
201
+ let password = await retrievePasswordForSignInUsername ( ) ,
202
+ let application = initialisePublicClientApplication ( )
203
+ else {
204
+ XCTFail ( " Something went wrong " )
205
+ return
206
+ }
207
+
208
+ let authenticationContextId = " c4 "
209
+ let authenticationContextRequestClaimJson = " { \" access_token \" :{ \" acrs \" :{ \" essential \" :true, \" value \" : \" \( authenticationContextId) \" }}} "
210
+ let authenticationContextATClaimJson = " \" acrs \" :[ \" \( authenticationContextId) \" ] "
211
+
212
+ let parameters = MSALNativeAuthSignInParameters ( username: username)
213
+ parameters. password = password
214
+ var error : NSError ? = nil
215
+
216
+ parameters. claimsRequest = MSALClaimsRequest ( jsonString: authenticationContextRequestClaimJson,
217
+ error: & error)
218
+
219
+ let signInExpectation = expectation ( description: " signing in " )
220
+ let signInDelegateSpy = SignInPasswordStartDelegateSpy ( expectation: signInExpectation)
221
+
222
+ application. signIn ( parameters: parameters, delegate: signInDelegateSpy)
223
+
224
+ await fulfillment ( of: [ signInExpectation] )
225
+
226
+ guard signInDelegateSpy. onSignInAwaitingMFACalled, let awaitingMFAState = signInDelegateSpy. newStateAwaitingMFA else {
227
+ XCTFail ( " Awaiting MFA not called " )
228
+ return
229
+ }
230
+
231
+ // Request to send challenge to the default strong auth method
232
+ let mfaExpectation = expectation ( description: " mfa " )
233
+ let mfaDelegateSpy = MFARequestChallengeDelegateSpy ( expectation: mfaExpectation)
234
+
235
+ awaitingMFAState. requestChallenge ( delegate: mfaDelegateSpy)
236
+
237
+ await fulfillment ( of: [ mfaExpectation] )
238
+
239
+ guard mfaDelegateSpy. onSelectionRequiredCalled, let mfaRequiredState = mfaDelegateSpy. newStateMFARequired, let authMethod = mfaDelegateSpy. authMethods? . first else {
240
+ XCTFail ( " Selection required not triggered " )
241
+ return
242
+ }
243
+
244
+ XCTAssertTrue ( authMethod. channelTargetType. isEmailType)
245
+
246
+ // Request to send challenge to a specific strong auth method
247
+
248
+ let mfaSendChallengeExpectation = expectation ( description: " mfa " )
249
+ let mfaSendChallengeDelegateSpy = MFARequestChallengeDelegateSpy ( expectation: mfaSendChallengeExpectation)
250
+ mfaRequiredState. requestChallenge ( authMethod: authMethod, delegate: mfaSendChallengeDelegateSpy)
251
+
252
+ await fulfillment ( of: [ mfaSendChallengeExpectation] )
253
+
254
+ guard mfaSendChallengeDelegateSpy. onVerificationRequiredCalled, let newMfaRequiredState = mfaSendChallengeDelegateSpy. newStateMFARequired else {
255
+ XCTFail ( " Challenge not sent to MFA method " )
256
+ return
257
+ }
258
+
259
+ XCTAssertNotNil ( mfaSendChallengeDelegateSpy. sentTo)
260
+ XCTAssertNotNil ( mfaSendChallengeDelegateSpy. codeLength)
261
+ XCTAssertTrue ( mfaSendChallengeDelegateSpy. channelTargetType!. isEmailType)
262
+
263
+ guard let code = await retrieveCodeFor ( email: username) else {
264
+ XCTFail ( " OTP code could not be retrieved " )
265
+ return
266
+ }
267
+
268
+ let submitChallengeExpectation = expectation ( description: " submitChallenge " )
269
+ let mfaSubmitChallengeDelegateSpy = MFASubmitChallengeDelegateSpy ( expectation: submitChallengeExpectation)
270
+
271
+ newMfaRequiredState. submitChallenge ( challenge: code, delegate: mfaSubmitChallengeDelegateSpy)
272
+
273
+ await fulfillment ( of: [ submitChallengeExpectation] )
274
+
275
+ XCTAssertTrue ( mfaSubmitChallengeDelegateSpy. onSignInCompletedCalled)
276
+ XCTAssertNotNil ( mfaSubmitChallengeDelegateSpy. result)
277
+ XCTAssertNotNil ( mfaSubmitChallengeDelegateSpy. result? . idToken)
278
+ XCTAssertEqual ( mfaSubmitChallengeDelegateSpy. result? . account. username, username)
279
+
280
+ let geAccessTokenExpectation = expectation ( description: " get access token " )
281
+ let credentialsDelegateSpy = CredentialsDelegateSpy ( expectation: geAccessTokenExpectation)
282
+
283
+ signInDelegateSpy. result? . getAccessToken ( parameters: MSALNativeAuthGetAccessTokenParameters ( ) , delegate: credentialsDelegateSpy)
284
+
285
+ await fulfillment ( of: [ geAccessTokenExpectation] )
286
+
287
+ XCTAssertTrue ( credentialsDelegateSpy. onAccessTokenRetrieveCompletedCalled)
288
+ XCTAssertNotNil ( credentialsDelegateSpy. result? . accessToken)
289
+
290
+ let atParts = credentialsDelegateSpy. result? . accessToken. components ( separatedBy: " . " )
291
+
292
+ // It should have 3 parts
293
+ guard let atParts, atParts. count == 3 else {
294
+ XCTFail ( " Invalid Access token received " )
295
+ return
296
+ }
297
+
298
+ // We need to use the middle part
299
+ var atBody : String ! = atParts [ 1 ]
300
+
301
+ //There could be the case that the length of the access token is not a multiple of 4 so we pad it with "="
302
+ let length = Double ( atBody. lengthOfBytes ( using: String . Encoding. utf8) )
303
+ let requiredLength = 4 * ceil( length / 4.0 )
304
+ let paddingLength = requiredLength - length
305
+ if paddingLength > 0 {
306
+ let padding = " " . padding ( toLength: Int ( paddingLength) , withPad: " = " , startingAt: 0 )
307
+ atBody = atBody + padding
308
+ }
309
+
310
+ let atEncodedData = Data ( base64Encoded: atBody!, options: . ignoreUnknownCharacters)
311
+ let atString = String ( data: atEncodedData!, encoding: . utf8) !
312
+
313
+ XCTAssertTrue ( atString. contains ( authenticationContextATClaimJson) )
314
+ }
315
+
195
316
// MARK: private methods
196
317
197
318
private func signInUsernameAndPassword( username: String , password: String ) async -> AwaitingMFAState ? {
0 commit comments