Skip to content

Commit fc74dfe

Browse files
authored
Add more details when reset password required error is received (#2382)
* add dedicated error description when reset password required error is returned from token endpoint * handle reset password required during token refresh * Add new unit tests for reset password required error * update changelog file
1 parent 394bcd6 commit fc74dfe

File tree

7 files changed

+71
-3
lines changed

7 files changed

+71
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## [Next release]:
2+
* Add native auth instructions to error description when reset password required is returned (#2582)
3+
14
## [1.6.1]:
25
* Support extra query parameters on logout endpoint (#2339)
36
* Add support functions to help broker improve cross cloud experience (#2361)

MSAL/src/native_auth/network/errors/MSALNativeAuthESTSApiErrorCodes.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ enum MSALNativeAuthESTSApiErrorCodes: Int {
2828
case invalidCredentials = 50126
2929
case userNotHaveAPassword = 500222
3030
case invalidRequestParameter = 90100
31+
case resetPasswordRequired = 50142
3132
}

MSAL/src/native_auth/network/errors/MSALNativeAuthErrorMessage.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ enum MSALNativeAuthErrorMessage {
4545
static let unexpectedResponseBody = "Unexpected response body received"
4646
static let unexpectedChallengeType = "Unexpected challenge type"
4747
static let refreshTokenMFARequiredError = "Multi-factor authentication is required, which can't be fulfilled as part of this flow. Please sign out and perform a new sign in operation. More information: "
48+
static let passwordResetRequired = "User password change is required, which can't be fulfilled as part of this flow. Please reset the password and perform a new sign in operation. More information: "
4849
}
4950

5051
// swiftlint:enable line_length

MSAL/src/native_auth/network/responses/validator/token/MSALNativeAuthTokenResponseValidator.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,19 @@ final class MSALNativeAuthTokenResponseValidator: MSALNativeAuthTokenResponseVal
139139
apiError: MSALNativeAuthTokenResponseError,
140140
context: MSIDRequestContext
141141
) -> MSALNativeAuthTokenValidatedResponse {
142+
var apiError = apiError
143+
if apiError.errorCodes?.contains(MSALNativeAuthESTSApiErrorCodes.resetPasswordRequired.rawValue) ?? false {
144+
let customErrorDescription = MSALNativeAuthErrorMessage.passwordResetRequired + (apiError.errorDescription ?? "")
145+
apiError = MSALNativeAuthTokenResponseError(
146+
error: apiError.error,
147+
subError: apiError.subError,
148+
errorDescription: customErrorDescription,
149+
errorCodes: apiError.errorCodes,
150+
errorURI: apiError.errorURI,
151+
innerErrors: apiError.innerErrors,
152+
continuationToken: apiError.continuationToken,
153+
correlationId: apiError.correlationId)
154+
}
142155
return handleInvalidResponseErrorCodes(
143156
apiError,
144157
context: context,
@@ -206,7 +219,8 @@ final class MSALNativeAuthTokenResponseValidator: MSALNativeAuthTokenResponseVal
206219
case .invalidCredentials:
207220
return .invalidPassword(apiError)
208221
case .userNotHaveAPassword,
209-
.invalidRequestParameter:
222+
.invalidRequestParameter,
223+
.resetPasswordRequired:
210224
return .generalError(apiError)
211225
}
212226
}
@@ -219,7 +233,8 @@ final class MSALNativeAuthTokenResponseValidator: MSALNativeAuthTokenResponseVal
219233
case .userNotFound,
220234
.invalidCredentials,
221235
.userNotHaveAPassword,
222-
.invalidRequestParameter:
236+
.invalidRequestParameter,
237+
.resetPasswordRequired:
223238
return .invalidRequest(apiError)
224239
}
225240
}

MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult+Internal.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ extension MSALNativeAuthUserAccountResult {
9090
let errorCodes = error.userInfo[MSALSTSErrorCodesKey] as? [Int] ?? []
9191
if isMFARequiredError(errorCodes: errorCodes) {
9292
message = MSALNativeAuthErrorMessage.refreshTokenMFARequiredError + message
93+
} else if isResetPasswordRequiredError(errorCodes: errorCodes) {
94+
message = MSALNativeAuthErrorMessage.passwordResetRequired + message
9395
}
9496
let correlationId = correlationIdFromMSALError(error: error) ?? context.correlationId()
9597
return RetrieveAccessTokenError(type: .generalError, message: message, correlationId: correlationId, errorCodes: errorCodes)
@@ -103,4 +105,8 @@ extension MSALNativeAuthUserAccountResult {
103105
let mfaRequiredErrorCode = 50076
104106
return errorCodes.contains(mfaRequiredErrorCode)
105107
}
108+
109+
private func isResetPasswordRequiredError(errorCodes: [Int]) -> Bool {
110+
return errorCodes.contains(MSALNativeAuthESTSApiErrorCodes.resetPasswordRequired.rawValue)
111+
}
106112
}

MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthTokenResponseValidatorTests.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,29 @@ final class MSALNativeAuthTokenResponseValidatorTest: MSALNativeAuthTestCase {
168168
}
169169
}
170170

171+
func test_invalidRequestResetPasswordRequired_theErrorDescriptionContainsCorrectInformation() {
172+
let continuationTokenResponse = "ct"
173+
let description = "reset password required"
174+
let errorCodes = [50142]
175+
let error = MSALNativeAuthTokenResponseError(error: .invalidRequest, subError: nil, errorDescription: description, errorCodes: errorCodes, errorURI: nil, innerErrors: nil, continuationToken: continuationTokenResponse)
176+
177+
let context = MSALNativeAuthRequestContext(correlationId: defaultUUID)
178+
let result = sut.validate(context: context, msidConfiguration: MSALNativeAuthConfigStubs.msidConfiguration, result: .failure(error))
179+
180+
guard case .error(let errorType) = result else {
181+
return XCTFail("Unexpected response")
182+
}
183+
guard case .invalidRequest(let validatedError) = errorType else {
184+
return XCTFail("Unexpected Error")
185+
}
186+
XCTAssertEqual(validatedError.error, .invalidRequest)
187+
XCTAssertEqual(validatedError.errorDescription, MSALNativeAuthErrorMessage.passwordResetRequired + description)
188+
XCTAssertEqual(validatedError.errorCodes, errorCodes)
189+
XCTAssertNil(validatedError.subError)
190+
XCTAssertNil(validatedError.innerErrors)
191+
XCTAssertNil(validatedError.errorURI)
192+
}
193+
171194
func test_invalidGrantMFARequired_triggerStrongAuthRequiredResponse() {
172195
let continuationTokenResponse = "ct"
173196
let error = MSALNativeAuthTokenResponseError(error: .invalidGrant, subError: .mfaRequired, errorDescription: nil, errorCodes: nil, errorURI: nil, innerErrors: nil, continuationToken: continuationTokenResponse)

MSAL/test/unit/native_auth/public/MSALNativeAuthUserAccountResultTests.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,26 @@ class MSALNativeAuthUserAccountResultTests: XCTestCase {
388388
let error = NSError(domain: "", code: 1, userInfo: userInfo)
389389
silentTokenProviderFactoryMock.silentTokenProvider.error = error
390390
let delegateExp = expectation(description: "delegateDispatcher delegate exp")
391-
let expectedError = RetrieveAccessTokenError(type: .generalError, message: MSALNativeAuthErrorMessage.refreshTokenMFARequiredError + message, correlationId: correlationId, errorCodes: [50076], errorUri: nil)
391+
let expectedError = RetrieveAccessTokenError(type: .generalError, message: MSALNativeAuthErrorMessage.refreshTokenMFARequiredError + message, correlationId: correlationId, errorCodes: errorCodes, errorUri: nil)
392+
let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError)
393+
sut.getAccessToken(correlationId: correlationId,
394+
delegate: delegate)
395+
396+
await fulfillment(of: [delegateExp])
397+
}
398+
399+
func test_errorWithResetPasswordRequiredErrorCode_ErrorMessageShouldContainsCorrectMessage() async {
400+
let correlationId = UUID()
401+
let errorCodes = [50142]
402+
let message = "message"
403+
let userInfo: [String : Any] = [
404+
MSALErrorDescriptionKey: message,
405+
MSALSTSErrorCodesKey: errorCodes
406+
]
407+
let error = NSError(domain: "", code: 1, userInfo: userInfo)
408+
silentTokenProviderFactoryMock.silentTokenProvider.error = error
409+
let delegateExp = expectation(description: "delegateDispatcher delegate exp")
410+
let expectedError = RetrieveAccessTokenError(type: .generalError, message: MSALNativeAuthErrorMessage.passwordResetRequired + message, correlationId: correlationId, errorCodes: errorCodes, errorUri: nil)
392411
let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError)
393412
sut.getAccessToken(correlationId: correlationId,
394413
delegate: delegate)

0 commit comments

Comments
 (0)