Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
4F5727E327F27F1A0008CDA4 /* SFSDKPrimingRecordsResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F5727DC27F27F1A0008CDA4 /* SFSDKPrimingRecordsResponse.h */; settings = {ATTRIBUTES = (Public, ); }; };
4F5727E427F27F1A0008CDA4 /* SFSDKPrimingRecordsResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F5727E227F27F1A0008CDA4 /* SFSDKPrimingRecordsResponse.m */; };
4F5A49502E98711600C89DDD /* ScopeParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5A494F2E98711600C89DDD /* ScopeParser.swift */; };
4FOAUTHEC012E98711600C89DDD /* SFOAuthErrorCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FOAUTHEC002E98711600C89DDD /* SFOAuthErrorCode.swift */; };
4FOAUTHECT012E98711600C89DDD /* SFOAuthErrorCodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FOAUTHECT002E98711600C89DDD /* SFOAuthErrorCodeTests.swift */; };
4F5A49582E98B0F800C89DDD /* ScopeParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5A49572E98B0F800C89DDD /* ScopeParserTests.swift */; };
4F755F5820D48F8600CE4E0E /* NSString+SFAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F755F4120D48D6700CE4E0E /* NSString+SFAdditionsTests.m */; };
4F7EB40D1BFFC88200768720 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8280EC1716E15FFC00768DE8 /* MessageUI.framework */; };
Expand Down Expand Up @@ -599,6 +601,8 @@
4F5727DC27F27F1A0008CDA4 /* SFSDKPrimingRecordsResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SFSDKPrimingRecordsResponse.h; sourceTree = "<group>"; };
4F5727E227F27F1A0008CDA4 /* SFSDKPrimingRecordsResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SFSDKPrimingRecordsResponse.m; sourceTree = "<group>"; };
4F5A494F2E98711600C89DDD /* ScopeParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScopeParser.swift; sourceTree = "<group>"; };
4FOAUTHEC002E98711600C89DDD /* SFOAuthErrorCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFOAuthErrorCode.swift; sourceTree = "<group>"; };
4FOAUTHECT002E98711600C89DDD /* SFOAuthErrorCodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SFOAuthErrorCodeTests.swift; path = SalesforceSDKCoreTests/SFOAuthErrorCodeTests.swift; sourceTree = SOURCE_ROOT; };
4F5A49572E98B0F800C89DDD /* ScopeParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScopeParserTests.swift; path = ../SalesforceSDKCoreTests/ScopeParserTests.swift; sourceTree = "<group>"; };
4F755F4120D48D6700CE4E0E /* NSString+SFAdditionsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "NSString+SFAdditionsTests.m"; path = "SalesforceSDKCoreTests/NSString+SFAdditionsTests.m"; sourceTree = SOURCE_ROOT; };
4F7EB3F71BFFC87600768720 /* SDKCommonNSDataTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDKCommonNSDataTests.m; path = SalesforceSDKCoreTests/SDKCommonNSDataTests.m; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -1105,6 +1109,7 @@
B7355248228E84AF001C7759 /* SFSDKLogoutBlocker.h */,
B7A901BD228E4DFA0036D749 /* SFSDKLogoutBlocker.m */,
399A11508BCB47F490DFB724 /* SFOAuthCoordinatorLightningURLTests.swift */,
4FOAUTHECT002E98711600C89DDD /* SFOAuthErrorCodeTests.swift */,
4F9E052C2DD6A06F00548985 /* SFSDKOAuthTokenEndpointResponseTests.m */,
69848CBB2364063E00893E57 /* SFSDKPushNotificationDataProvider.h */,
69848CBC2364063E00893E57 /* SFSDKPushNotificationDataProvider.m */,
Expand Down Expand Up @@ -1198,6 +1203,7 @@
4F5A494F2E98711600C89DDD /* ScopeParser.swift */,
23D96B6E2E145AC20004B06A /* DomainDiscoveryCoordinator.swift */,
4F8A3B002CEC202F00ECDC76 /* JwtAccessToken.swift */,
4FOAUTHEC002E98711600C89DDD /* SFOAuthErrorCode.swift */,
4F96FCC61BFD32130022F021 /* SFOAuthCoordinator.h */,
4F96FCC71BFD32130022F021 /* SFOAuthCoordinator.m */,
4F96FCC51BFD32130022F021 /* SFOAuthCoordinator+Internal.h */,
Expand Down Expand Up @@ -2250,6 +2256,7 @@
4F3ECD8A2EBBD150005020A6 /* SFOAuthCoordinatorTests.m in Sources */,
4FA1B2C32F0E000000000001 /* LoginForAdminTests.swift in Sources */,
1A31073F5F374B9EB1162F2E /* SFOAuthCoordinatorLightningURLTests.swift in Sources */,
4FOAUTHECT012E98711600C89DDD /* SFOAuthErrorCodeTests.swift in Sources */,
4F9E05322DD6A08000548985 /* SFSDKOAuthTokenEndpointResponseTests.m in Sources */,
4F06AF8D1C49A18E00F70798 /* SalesforceSDKManagerTests.m in Sources */,
237C186C2E44FCAE0008015C /* EncryptStreamTests.swift in Sources */,
Expand Down Expand Up @@ -2444,6 +2451,7 @@
A3C7476129F709EB00D72B7F /* BiometricAuthenticationManagerInternal.swift in Sources */,
B7FB26DB1F78096300FB25A2 /* SFSDKIDPErrorHandler.m in Sources */,
4F5A49502E98711600C89DDD /* ScopeParser.swift in Sources */,
4FOAUTHEC012E98711600C89DDD /* SFOAuthErrorCode.swift in Sources */,
B773CCF81F8200BD00D2D1B2 /* SFSDKIDPLoginRequestCommand.m in Sources */,
CE4CE3931C0E526A009F6029 /* SFUserAccountIdentity.m in Sources */,
23945B712D78E4A60060B195 /* NotificationType.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -478,15 +478,15 @@ - (void)beginJwtTokenExchangeFlow {
id json = nil;
json = [SFJsonUtils objectFromJSONData:data];
if (json == nil) {
NSError *error = [SFSDKOAuth2 errorWithType:kSFOAuthErrorTypeJWTLaunchFailed
NSError *error = [SFSDKOAuth2 errorWithType:@"jwt_launch_failed"
description:@"Error parsing JWT token exchange response."
underlyingError:[SFJsonUtils lastError]];
[self notifyDelegateOfFailure:error authInfo:self.authInfo];
return;
}
if (![json isKindOfClass:[NSDictionary class]]) {
NSString *errorDesc = [NSString stringWithFormat:@"Expected NSDictionary for JWT token response, received %@ instance.", NSStringFromClass([json class])];
NSError *error = [SFSDKOAuth2 errorWithType:kSFOAuthErrorTypeJWTLaunchFailed
NSError *error = [SFSDKOAuth2 errorWithType:@"jwt_launch_failed"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is hardcoded now because the client-side is creating the error?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct — this is a client-side error, not a server wire value. The JWT response is malformed (wrong type), so the SDK generates the error locally rather than receiving it from the server. jwt_launch_failed was previously held in the kSFOAuthErrorTypeJWTLaunchFailed constant (now deprecated), but since this error string is not a server-defined wire value it isn't in SFOAuthErrorCode. The raw string is used directly here since the deprecated constant is no longer the preferred reference.

description:errorDesc];
[self notifyDelegateOfFailure:error authInfo:self.authInfo];
return;
Expand Down Expand Up @@ -670,7 +670,7 @@ - (void)handleResponse:(SFSDKOAuthTokenEndpointResponse *)response {
[SFSDKCoreLogger d:[self class] format:@"Refresh attempt timed out after %f seconds.", self.timeout];
[self stopAuthentication];
}
BOOL isUnsupportedGrantType = [response.error.tokenEndpointErrorCode isEqualToString:kSFOAuthErrorTypeUnsupportedGrantType];
BOOL isUnsupportedGrantType = (response.error.errorCode == SFOAuthErrorCodeUnsupportedGrantType);
BOOL isLightningURL = [self.credentials.domain containsString:@".lightning."];
if (isUnsupportedGrantType && isLightningURL) {
[SFSDKCoreLogger e:[self class] format:@"Code exchange failed with unsupported_grant_type against Lightning URL: %@. Lightning URLs do not support authorization_code grant type. Use a My Domain login server URL instead.", self.credentials.domain];
Expand All @@ -697,12 +697,12 @@ - (NSError *)checkFrontdoorResponseForErrors:(NSURL *)requestUrl {
NSString *errorDescription = [requestUrl sfsdk_valueForParameterName:kSFOAuthErrorDescription];
if (foundValidEcValue) {
[SFSDKCoreLogger d:[self class] format:@"%@ IDP Authcode redirect response encountered an ec=301 or 302 redirect: %@", NSStringFromSelector(_cmd), requestUrl];
error = [SFSDKOAuth2 errorWithType:kSFOAuthErrorTypeMalformedResponse description:@"IDP Authcode redirect response encountered an ec=301 or 302 redirect"];
error = [SFSDKOAuth2 errorWithType:@"malformed_response" description:@"IDP Authcode redirect response encountered an ec=301 or 302 redirect"];
} else if (errorCode) {
error = [SFSDKOAuth2 errorWithType:errorCode description:errorDescription];
} else if (![requestUrl fragment] && ![requestUrl query]){
[SFSDKCoreLogger d:[self class] format:@"%@ Error: IDP Authcode response has no payload: %@", NSStringFromSelector(_cmd), requestUrl];
error = [SFSDKOAuth2 errorWithType:kSFOAuthErrorTypeMalformedResponse description:@"IDP Authcode redirect response has no payload"];
error = [SFSDKOAuth2 errorWithType:@"malformed_response" description:@"IDP Authcode redirect response has no payload"];
}
return error;
}
Expand Down Expand Up @@ -743,7 +743,7 @@ - (void)handleUserAgentResponse:(NSURL *)requestUrl {
response = [requestUrl query];
} else {
[SFSDKCoreLogger d:[self class] format:@"%@ Error: response has no payload: %@", NSStringFromSelector(_cmd), requestUrl];
NSError *error = [SFSDKOAuth2 errorWithType:kSFOAuthErrorTypeMalformedResponse description:@"redirect response has no payload"];
NSError *error = [SFSDKOAuth2 errorWithType:@"malformed_response" description:@"redirect response has no payload"];
[self notifyDelegateOfFailure:error authInfo:self.authInfo];
response = nil;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
SFOAuthErrorCode.swift
SalesforceSDKCore

Copyright (c) 2026-present, salesforce.com, inc. All rights reserved.

Redistribution and use of this software in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions
and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of salesforce.com, inc. nor the names of its contributors may be used to
endorse or promote products derived from this software without specific prior written
permission of salesforce.com, inc.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import Foundation

/// Typed representation of the OAuth token endpoint error values defined by the
/// Salesforce server in OauthErrorCode.java (core/identity-common-api).
///
/// Use ``from(_:)`` to parse the raw `error` string from a token endpoint response.
@objc public enum SFOAuthErrorCode: Int, CaseIterable {
case unknown = 0
case accessDenied
case appBlocked
case appNotFound
case authorizationPending
case badJtiClaim
case appAttestationFailed
case appAttestationFailedRetry
case ecAppPolicyNotFound
case exceededRegistrationLimit
case failCloseAppBlocked
case failedRegistration
case immediateUnsuccessful
case installationError
case invalidAppAccess
case invalidAssertionType
case invalidBasicAuthHeader
case invalidClient
case invalidClientId
case invalidDpopProof
case invalidDistributionState
case invalidExpid
case invalidGrant
case invalidOtp
case invalidRequest
case invalidScope
case invalidSessionLevel
case invalidToken
case loginError
case oauthFlowDisabled
case oauthPolicyNotFound
case otpError
case redirectUriMissing
case redirectUriMismatch
case registrationError
case serverError
case serviceUnavailable
case slowDown
case systemDown
case unknownError
case unsupportedExpid
case unsupportedGrantType
case unsupportedResponseType
case unsupportedTokenType
case useDpopNonce

/// Returns the ``SFOAuthErrorCode`` whose wire value matches `string`,
/// or `.unknown` if `string` is nil, empty, or not recognized.
public static func from(_ string: String?) -> SFOAuthErrorCode {
guard let string = string, !string.isEmpty else { return .unknown }
return SFOAuthErrorCode.allCases.first { $0.wireValue == string } ?? .unknown
}
}

/// Objective-C–accessible bridge for ``SFOAuthErrorCode``.
/// Use `SFOAuthErrorCodeHelper.from(_:)` from Objective-C to parse error wire strings.
@objc public class SFOAuthErrorCodeHelper: NSObject {
/// Returns the integer raw value of the ``SFOAuthErrorCode`` matching `string`,
/// or the raw value of `.unknown` (0) if not recognized.
@objc public static func from(_ string: String?) -> NSInteger {
return SFOAuthErrorCode.from(string).rawValue
}
}

public extension SFOAuthErrorCode {
/// The wire string value sent in the token endpoint error JSON response.
/// Returns `nil` for `.unknown`.
var wireValue: String? {
switch self {
case .unknown: return nil
case .accessDenied: return "access_denied"
case .appBlocked: return "app_blocked"
case .appNotFound: return "app_not_found"
case .authorizationPending: return "authorization_pending"
case .badJtiClaim: return "bad_jti_claim"
case .appAttestationFailed: return "client_blocked"
case .appAttestationFailedRetry: return "client_blocked_retry"
case .ecAppPolicyNotFound: return "ecapp_policy_not_found"
case .exceededRegistrationLimit: return "exceeded_registration_limit"
case .failCloseAppBlocked: return "fail_close_app_blocked"
case .failedRegistration: return "failed_registration"
case .immediateUnsuccessful: return "immediate_unsuccessful"
case .installationError: return "installation_error"
case .invalidAppAccess: return "invalid_app_access"
case .invalidAssertionType: return "invalid_assertion_type"
case .invalidBasicAuthHeader: return "invalid_basic_auth_header"
case .invalidClient: return "invalid_client"
case .invalidClientId: return "invalid_client_id"
case .invalidDpopProof: return "invalid_dpop_proof"
case .invalidDistributionState: return "invalid_distribution_state"
case .invalidExpid: return "invalid_expid"
case .invalidGrant: return "invalid_grant"
case .invalidOtp: return "invalid_otp"
case .invalidRequest: return "invalid_request"
case .invalidScope: return "invalid_scope"
case .invalidSessionLevel: return "invalid_session_level"
case .invalidToken: return "invalid_token"
case .loginError: return "login_error"
case .oauthFlowDisabled: return "oauth_flow_disabled"
case .oauthPolicyNotFound: return "oauth_policy_not_found"
case .otpError: return "otp_error"
case .redirectUriMissing: return "redirect_uri_missing"
case .redirectUriMismatch: return "redirect_uri_mismatch"
case .registrationError: return "registration_error"
case .serverError: return "server_error"
case .serviceUnavailable: return "service_unavailable"
case .slowDown: return "slow_down"
case .systemDown: return "system_down"
case .unknownError: return "unknown_error"
case .unsupportedExpid: return "unsupported_expid"
case .unsupportedGrantType: return "unsupported_grant_type"
case .unsupportedResponseType: return "unsupported_response_type"
case .unsupportedTokenType: return "unsupported_token_type"
case .useDpopNonce: return "use_dpop_nonce"
@unknown default: return nil
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) NSString *tokenEndpointErrorCode;
@property (nonatomic, readonly) NSString *tokenEndpointErrorDescription;
@property (nonatomic, readonly) NSError *error;
/// Typed enum representation of ``tokenEndpointErrorCode``.
/// The enum is declared in Swift as `SFOAuthErrorCode`; use that type when calling
/// from Swift. From Objective-C, the type is `NSInteger`.
/// Use this property instead of string-comparing ``tokenEndpointErrorCode``.
@property (nonatomic, readonly) NSInteger errorCode;
@end

@interface SFSDKOAuthTokenEndpointRequest : NSObject
Expand Down
Loading
Loading