Skip to main content

Overview

Keychain operations can fail for various reasons: incorrect accessibility settings, missing items, device lock state, or invalid parameters. Proper error handling ensures your app responds gracefully to these situations.

Error Handling Approaches

SAMKeychain provides two ways to handle errors:

Simple Approach: Return Value

For basic operations, check the return value:
Return Value Checking
// Setting a password
BOOL success = [SAMKeychain setPassword:@"myPassword" 
                             forService:@"com.myapp.MyApp" 
                                account:@"user@example.com"];

if (!success) {
    NSLog(@"Failed to save password");
    // Handle the error
}

// Deleting a password
BOOL deleted = [SAMKeychain deletePasswordForService:@"com.myapp.MyApp" 
                                             account:@"user@example.com"];

if (!deleted) {
    NSLog(@"Failed to delete password");
}

Detailed Approach: NSError

For detailed error information, use the error: parameter variants:
NSError Handling
NSError *error = nil;
BOOL success = [SAMKeychain setPassword:@"myPassword" 
                             forService:@"com.myapp.MyApp" 
                                account:@"user@example.com" 
                                  error:&error];

if (!success && error) {
    NSLog(@"Error code: %ld", (long)error.code);
    NSLog(@"Error domain: %@", error.domain);
    NSLog(@"Error description: %@", error.localizedDescription);
    
    // Handle specific error codes
    if (error.code == errSecItemNotFound) {
        // Item doesn't exist
    }
}
Always use the error: variants for production code to get detailed error information for debugging and user feedback.

Error Codes

SAMKeychain Error Codes

SAMKeychain defines its own error codes in the domain com.samsoffes.samkeychain:
SAMKeychain Error Domain
extern NSString *const kSAMKeychainErrorDomain; // @"com.samsoffes.samkeychain"
Error CodeConstantDescription
-1001SAMKeychainErrorBadArgumentsInvalid or missing required parameters

Bad Arguments Error

This occurs when required parameters are missing or invalid:
Handling Bad Arguments
NSError *error = nil;
BOOL success = [SAMKeychain setPassword:nil  // Missing password
                             forService:@"com.myapp.MyApp" 
                                account:@"user@example.com" 
                                  error:&error];

if (!success && error.code == SAMKeychainErrorBadArguments) {
    NSLog(@"Invalid parameters provided");
    // Typically: service, account, or password is nil or empty
}
SAMKeychainErrorBadArguments indicates a programming error. Check that you’re providing non-nil values for service, account, and password.

System Error Codes

Most errors come from Apple’s Security framework (defined in SecBase.h). These are returned with the OSStatus type:
Meaning: Operation completed successfully
if (error.code == errSecSuccess || error == nil) {
    // Success - no error occurred
}
When successful, the error parameter is typically nil, but checking for errSecSuccess is also valid.
Meaning: The keychain item does not existCommon causes:
  • Attempting to fetch a password that was never saved
  • Wrong service or account name
  • Item was already deleted
Handling Item Not Found
NSError *error = nil;
NSString *password = [SAMKeychain passwordForService:@"com.myapp.MyApp" 
                                             account:@"user@example.com" 
                                               error:&error];

if (error.code == errSecItemNotFound) {
    NSLog(@"No password found for this account");
    // This is often not an error - just means user hasn't logged in yet
}
This is often expected behavior (e.g., on first launch) and doesn’t always indicate a problem.
Meaning: An item with the same service and account already existsWhen this occurs:
  • Manually adding items with Security framework APIs
  • Race conditions in multi-threaded code
Handling Duplicates
if (error.code == errSecDuplicateItem) {
    NSLog(@"Item already exists");
    // SAMKeychain normally handles this automatically by updating
    // If you see this, it may indicate an unusual condition
}
SAMKeychain automatically handles duplicates by updating existing items, so you should rarely see this error.
Meaning: User interaction is required but not possibleCommon causes:
  • Device is locked and item requires device unlock
  • Accessibility setting prevents access in current state
  • Background operation attempting to access protected item
Handling Interaction Not Allowed
NSError *error = nil;
NSString *password = [SAMKeychain passwordForService:@"com.myapp.MyApp" 
                                             account:@"user@example.com" 
                                               error:&error];

if (error.code == errSecInteractionNotAllowed) {
    NSLog(@"Cannot access keychain item - device may be locked");
    // Retry when device is unlocked or app is in foreground
}
This often indicates an accessibility mismatch. Review your accessibility settings (see Accessibility).
Meaning: Authentication or authorization failedCommon causes:
  • User denied access to keychain item
  • Insufficient privileges
  • Access control restrictions
Handling Auth Failed
if (error.code == errSecAuthFailed) {
    NSLog(@"Authorization failed");
    // User may have denied access or security policy prevents access
}
Meaning: One or more parameters passed to the function were invalid
if (error.code == errSecParam) {
    NSLog(@"Invalid parameter");
    // Check all parameters for correctness
}
Meaning: Function or operation not implemented
if (error.code == errSecUnimplemented) {
    NSLog(@"Operation not implemented on this platform");
}
Meaning: No keychain is availableCommon causes:
  • Keychain access not configured in provisioning profile
  • Simulator issues
  • Corrupted keychain
if (error.code == errSecNotAvailable) {
    NSLog(@"Keychain is not available");
    // Check provisioning profile and entitlements
}
Meaning: Memory allocation failure
if (error.code == errSecAllocate) {
    NSLog(@"Memory allocation failed");
    // Severe system error - very rare
}
Meaning: Unable to decode the provided data
if (error.code == errSecDecode) {
    NSLog(@"Failed to decode keychain data");
    // Data may be corrupted
}

Comprehensive Error Handling

Production-Ready Error Handling

Here’s a complete example of robust error handling:
Complete Error Handling
- (BOOL)savePassword:(NSString *)password 
          forAccount:(NSString *)account 
               error:(NSError **)outError {
    
    // Validate input
    if (!password || !account) {
        if (outError) {
            *outError = [NSError errorWithDomain:@"com.myapp.AppErrors" 
                                            code:1001 
                                        userInfo:@{
                NSLocalizedDescriptionKey: @"Password and account are required"
            }];
        }
        return NO;
    }
    
    // Attempt to save
    NSError *keychainError = nil;
    BOOL success = [SAMKeychain setPassword:password 
                                 forService:@"com.myapp.MyApp" 
                                    account:account 
                                      error:&keychainError];
    
    if (!success) {
        NSLog(@"Keychain error: %@ (code: %ld)", 
              keychainError.localizedDescription, 
              (long)keychainError.code);
        
        // Handle specific errors
        switch (keychainError.code) {
            case SAMKeychainErrorBadArguments:
                NSLog(@"Programming error: invalid arguments");
                break;
                
            case errSecNotAvailable:
                NSLog(@"Keychain not available - check entitlements");
                break;
                
            case errSecInteractionNotAllowed:
                NSLog(@"Device locked or accessibility prevents access");
                break;
                
            default:
                NSLog(@"Unexpected keychain error: %ld", (long)keychainError.code);
                break;
        }
        
        if (outError) {
            *outError = keychainError;
        }
        return NO;
    }
    
    return YES;
}

Using SAMKeychainQuery for Better Error Control

SAMKeychainQuery provides more granular error information:
SAMKeychainQuery Error Handling
SAMKeychainQuery *query = [[SAMKeychainQuery alloc] init];
query.service = @"com.myapp.MyApp";
query.account = @"user@example.com";
query.password = @"myPassword";

NSError *error = nil;
BOOL success = [query save:&error];

if (!success) {
    // Check if it's a SAMKeychain error
    if ([error.domain isEqualToString:kSAMKeychainErrorDomain]) {
        if (error.code == SAMKeychainErrorBadArguments) {
            NSLog(@"Missing required fields in query");
        }
    } else {
        // System error from Security framework
        NSLog(@"System error: %ld", (long)error.code);
    }
}

Common Error Scenarios

Scenario 1: First Launch (Item Not Found)

First Launch
NSError *error = nil;
NSString *password = [SAMKeychain passwordForService:@"com.myapp.MyApp" 
                                             account:@"user@example.com" 
                                               error:&error];

if (!password && error.code == errSecItemNotFound) {
    // Expected on first launch - no password saved yet
    NSLog(@"No saved password - this is the first launch");
    // Prompt user to log in
} else if (!password) {
    // Unexpected error
    NSLog(@"Error retrieving password: %@", error);
}

Scenario 2: Background Access Denied

Background Access
// In a background task
NSError *error = nil;
NSString *token = [SAMKeychain passwordForService:@"com.myapp.MyApp" 
                                          account:@"api-token" 
                                            error:&error];

if (error.code == errSecInteractionNotAllowed) {
    NSLog(@"Cannot access token in background - device is locked");
    // Schedule retry or use kSecAttrAccessibleAfterFirstUnlock
}
If you need background access, use kSecAttrAccessibleAfterFirstUnlock instead of kSecAttrAccessibleWhenUnlocked.

Scenario 3: User Feedback

User-Friendly Error Messages
- (void)displayUserFriendlyError:(NSError *)error {
    NSString *message;
    
    switch (error.code) {
        case errSecItemNotFound:
            message = @"Please log in to continue";
            break;
            
        case errSecInteractionNotAllowed:
            message = @"Please unlock your device to access this feature";
            break;
            
        case errSecAuthFailed:
            message = @"Access denied. Please check your permissions";
            break;
            
        case errSecNotAvailable:
            message = @"Unable to access secure storage. Please try again";
            break;
            
        default:
            message = @"An unexpected error occurred. Please try again";
            break;
    }
    
    UIAlertController *alert = [UIAlertController 
        alertControllerWithTitle:@"Error" 
                         message:message 
                  preferredStyle:UIAlertControllerStyleAlert];
    
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" 
                                              style:UIAlertActionStyleDefault 
                                            handler:nil]];
    
    [self presentViewController:alert animated:YES completion:nil];
}

Debugging Tips

Enable Verbose Logging

Debug Logging
#ifdef DEBUG
- (void)logKeychainOperation:(NSString *)operation 
                     success:(BOOL)success 
                       error:(NSError *)error {
    if (success) {
        NSLog(@"✅ %@ succeeded", operation);
    } else {
        NSLog(@"❌ %@ failed", operation);
        NSLog(@"   Domain: %@", error.domain);
        NSLog(@"   Code: %ld", (long)error.code);
        NSLog(@"   Description: %@", error.localizedDescription);
    }
}
#endif

Common Debugging Checks

1

Verify Parameters

Ensure service and account are non-nil, non-empty strings
2

Check Accessibility

Confirm accessibility is set appropriately for your use case
3

Review Entitlements

Verify keychain access groups in your app’s entitlements file
4

Test Device States

Test with device locked, unlocked, and after reboot
5

Monitor Console

Watch for Security framework messages in Console.app

Best Practices

Always use error parameters in production code for detailed error information

Handle errSecItemNotFound gracefully - it’s often expected, not an error

Set appropriate accessibility to avoid errSecInteractionNotAllowed

Provide user-friendly messages instead of exposing raw error codes

Don’t ignore errors from keychain operations

Don’t assume success - always check return values

Don’t crash on errors - handle them gracefully

Don’t expose sensitive error details to users

Error Reference Table

Error CodeConstantSeverityAction
0errSecSuccessInfoNone - success
-1001SAMKeychainErrorBadArgumentsHighFix code - check parameters
-25300errSecItemNotFoundLowOften expected - item doesn’t exist
-25299errSecDuplicateItemMediumUpdate instead of add
-25308errSecInteractionNotAllowedMediumCheck accessibility/device state
-25293errSecAuthFailedHighCheck permissions/access control
-25291errSecNotAvailableHighVerify entitlements
-50errSecParamHighInvalid parameter
-4errSecUnimplementedMediumFeature not supported
-108errSecAllocateCriticalSystem memory issue
-26275errSecDecodeMediumData corruption

Next Steps

Keychain Basics

Learn the fundamentals of keychain storage

Accessibility

Understand accessibility options to prevent access errors

Advanced Queries

Use SAMKeychainQuery for more control

Troubleshooting

Solutions to common keychain issues