Skip to content

Commit b089ff5

Browse files
committed
Adds proper handling of ID token validation errors
1 parent 5705eca commit b089ff5

File tree

6 files changed

+40
-13
lines changed

6 files changed

+40
-13
lines changed

App/ContentView.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,11 @@ struct ContentView: View {
2323

2424
Button {
2525
Task {
26-
#if WEB_AUTH_PLATFORM
2726
#if os(macOS)
2827
await viewModel.webLogin(presentationWindow: currentWindow)
2928
#else
3029
await viewModel.webLogin(presentationWindow: window)
3130
#endif
32-
#endif
3331
}
3432
} label: {
3533
VStack(spacing: 4) {
@@ -44,14 +42,11 @@ struct ContentView: View {
4442

4543
Button {
4644
Task {
47-
#if WEB_AUTH_PLATFORM
48-
4945
#if os(macOS)
5046
await viewModel.logout(presentationWindow: currentWindow)
5147
#else
5248
await viewModel.logout(presentationWindow: window)
5349
#endif
54-
#endif
5550
}
5651
} label: {
5752
Text("Logout")

App/ContentViewModel.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ final class ContentViewModel: ObservableObject {
99
@Published var isAuthenticated: Bool = false
1010
private let credentialsManager = CredentialsManager(authentication: Auth0.authentication())
1111

12-
#if WEB_AUTH_PLATFORM
1312
func webLogin(presentationWindow window: Auth0WindowRepresentable? = nil) async {
1413
isLoading = true
1514
errorMessage = nil
@@ -66,9 +65,7 @@ final class ContentViewModel: ObservableObject {
6665

6766
isLoading = false
6867
}
69-
70-
#endif
71-
68+
7269
func checkAuthentication() async {
7370
do {
7471
let credentials = try await credentialsManager.credentials()

Auth0/CredentialsManager.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -904,8 +904,11 @@ public struct CredentialsManager: Sendable {
904904
}
905905
case .failure(let error):
906906
complete()
907+
// ID token validation failures are deterministic — do not retry them
908+
if let cause = error.cause, isIDTokenValidationError(cause) {
909+
callback(.failure(CredentialsManagerError(code: .renewFailed, cause: cause)))
907910
// Check if we should retry based on error type and retry count
908-
if self.shouldRetryRenewal(for: error, retryCount: retryCount) {
911+
} else if self.shouldRetryRenewal(for: error, retryCount: retryCount) {
909912
// Calculate exponential backoff delay: 0.5s, 1s, 2s, etc.
910913
let delay = pow(2.0, Double(retryCount)) * 0.5
911914
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + delay) {
@@ -979,7 +982,11 @@ public struct CredentialsManager: Sendable {
979982
}
980983
case .failure(let error):
981984
complete()
982-
callback(.failure(CredentialsManagerError(code: .ssoExchangeFailed, cause: error)))
985+
if let cause = error.cause, isIDTokenValidationError(cause) {
986+
callback(.failure(CredentialsManagerError(code: .ssoExchangeFailed, cause: cause)))
987+
} else {
988+
callback(.failure(CredentialsManagerError(code: .ssoExchangeFailed, cause: error)))
989+
}
983990
}
984991
}
985992
}

Auth0/IDTokenValidator.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,24 @@ enum IDTokenDecodingError: Auth0Error {
4646
}
4747
}
4848

49+
/// Returns `true` if the given error is one of the known ID token validation error types.
50+
/// Use this instead of `cause is any Auth0Error` since many types (e.g. `AuthenticationError`,
51+
/// `CredentialsManagerError`) also conform to `Auth0Error` and would produce false positives.
52+
func isIDTokenValidationError(_ error: Error) -> Bool {
53+
return error is IDTokenDecodingError
54+
|| error is IDTokenIssValidator.ValidationError
55+
|| error is IDTokenSubValidator.ValidationError
56+
|| error is IDTokenAudValidator.ValidationError
57+
|| error is IDTokenExpValidator.ValidationError
58+
|| error is IDTokenIatValidator.ValidationError
59+
|| error is IDTokenNonceValidator.ValidationError
60+
|| error is IDTokenAzpValidator.ValidationError
61+
|| error is IDTokenAuthTimeValidator.ValidationError
62+
|| error is IDTokenOrgIDValidator.ValidationError
63+
|| error is IDTokenOrgNameValidator.ValidationError
64+
|| error is IDTokenSignatureValidator.ValidationError
65+
}
66+
4967
func validate(idToken: String,
5068
with context: IDTokenValidatorContext,
5169
signatureValidator: JWTAsyncValidator? = nil, // for testing

Auth0/MFA/Auth0MFAClient.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,17 @@ private extension Auth0MFAClient {
196196
nonce: nonce,
197197
organization: organization,
198198
from: result,
199-
callback: callback
199+
callback: { (verifyResult: Result<T, MFAVerifyError>) in
200+
switch verifyResult {
201+
case .failure(let error):
202+
if let cause = error.cause, isIDTokenValidationError(cause) {
203+
return callback(.failure(MFAVerifyError(cause: cause)))
204+
}
205+
callback(.failure(error))
206+
case .success(let value):
207+
callback(.success(value))
208+
}
209+
}
200210
)
201211
},
202212
parameters: payload,

Auth0/OAuth2Grant.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ struct PKCE: OAuth2Grant {
9191
case .failure(let error):
9292
// ID token validation failures are wrapped in AuthenticationError(cause:) where the
9393
// cause is an Auth0Error from the validator. Map these to .idTokenValidationFailed.
94-
if let cause = error.cause, cause is any Auth0Error {
94+
if let cause = error.cause, isIDTokenValidationError(cause) {
9595
return callback(.failure(WebAuthError(code: .idTokenValidationFailed, cause: cause)))
9696
}
9797
return callback(.failure(WebAuthError(code: .codeExchangeFailed, cause: error)))

0 commit comments

Comments
 (0)