Overview
SAMKeychain makes it easy to securely store passwords and sensitive data in the system keychain. This guide covers the different storage options and best practices for keeping your users’ data safe.
Basic Password Storage
The simplest way to store a password is using the class method that takes a service name and account identifier:
BOOL success = [SAMKeychain setPassword:@"mySecretPassword"
forService:@"MyApp"
account:@"user@example.com"];
if (success) {
NSLog(@"Password saved successfully");
}
The service name typically represents your app or the service the credentials are for, while the account identifies the specific user or account.
Storing vs. Updating
When you call setPassword:forService:account:, SAMKeychain automatically:
Checks if an item already exists for the given service and account
Updates the existing item if found
Creates a new item if not found
You don’t need to check whether the item exists first—SAMKeychain handles this for you.
// First time: creates new keychain item
[SAMKeychain setPassword:@"password123"
forService:@"MyApp"
account:@"user@example.com"];
// Second time: updates existing keychain item
[SAMKeychain setPassword:@"newPassword456"
forService:@"MyApp"
account:@"user@example.com"];
String vs. Data Storage
SAMKeychain provides two storage options depending on your data type:
String Passwords
For text-based passwords and tokens:
NSString *password = @"myPassword123";
[SAMKeychain setPassword:password
forService:@"MyApp"
account:@"user@example.com"];
Internally, strings are converted to UTF-8 encoded NSData before storage.
Binary Data
For binary data, encryption keys, or custom data formats:
// Store binary data (e.g., encryption key)
NSData *encryptionKey = [[NSData alloc] initWithBytes:keyBytes length:32];
[SAMKeychain setPasswordData:encryptionKey
forService:@"MyApp"
account:@"encryptionKey"];
// Store archived objects
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:myObject];
[SAMKeychain setPasswordData:archivedData
forService:@"MyApp"
account:@"cachedObject"];
Use setPasswordData: for non-text data like encryption keys, certificates, or serialized objects.
Error Handling
For production code, always check for errors:
NSError *error = nil;
BOOL success = [SAMKeychain setPassword:@"myPassword"
forService:@"MyApp"
account:@"user@example.com"
error:&error];
if (!success) {
NSLog(@"Failed to save password: %@", error.localizedDescription);
// Handle specific error codes
if (error.code == SAMKeychainErrorBadArguments) {
NSLog(@"Invalid arguments provided");
}
}
Choosing Accessibility Options
iOS and macOS provide different accessibility levels that control when keychain items can be accessed:
Understand the options
kSecAttrAccessibleWhenUnlocked - Data accessible only while device is unlocked (most secure)
kSecAttrAccessibleAfterFirstUnlock - Data accessible after first unlock since boot (good for background tasks)
kSecAttrAccessibleAlways - Data always accessible (least secure, deprecated in iOS 12+)
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly - Requires device passcode, never synced
kSecAttrAccessibleWhenUnlockedThisDeviceOnly - Never synced to iCloud or other devices
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly - Never synced, available after first unlock
Set the global accessibility type
#import <Security/Security.h>
// Set for all future keychain operations
[SAMKeychain setAccessibilityType:kSecAttrAccessibleWhenUnlocked];
// Now store passwords - they'll use this accessibility level
[SAMKeychain setPassword:@"secret"
forService:@"MyApp"
account:@"user@example.com"];
Choose based on your use case
Foreground apps : Use kSecAttrAccessibleWhenUnlocked for maximum security[SAMKeychain setAccessibilityType:kSecAttrAccessibleWhenUnlocked];
Background apps : Use kSecAttrAccessibleAfterFirstUnlock to access data while app runs in background[SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlock];
Non-syncing sensitive data : Use the ThisDeviceOnly variants[SAMKeychain setAccessibilityType:kSecAttrAccessibleWhenUnlockedThisDeviceOnly];
The default accessibility (when not set) is highly insecure. Always explicitly set an accessibility type appropriate for your use case.
Best Practices
Choose Meaningful Service and Account Names
// Good: Clear, consistent naming
[SAMKeychain setPassword:userPassword
forService:@"com.company.MyApp"
account:userEmail];
// Avoid: Generic names that might conflict
[SAMKeychain setPassword:userPassword
forService:@"app"
account:@"user"];
Use Reverse Domain Notation for Services
// Prevents conflicts with other apps
NSString *serviceName = @"com.yourcompany.yourapp";
[SAMKeychain setPassword:password
forService:serviceName
account:account];
Never Store Passwords in UserDefaults
// ❌ NEVER do this
[[NSUserDefaults standardUserDefaults] setObject:password forKey:@"password"];
// ✅ Always use SAMKeychain
[SAMKeychain setPassword:password forService:@"MyApp" account:account];
Set Accessibility Before First Save
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
// Configure accessibility early
[SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlock];
// Rest of your app initialization...
}
- (BOOL)savePassword:(NSString *)password forAccount:(NSString *)account {
// Validate inputs
if (!password || password.length == 0) {
NSLog(@"Password cannot be empty");
return NO;
}
if (!account || account.length == 0) {
NSLog(@"Account cannot be empty");
return NO;
}
// Store in keychain
NSError *error = nil;
BOOL success = [SAMKeychain setPassword:password
forService:@"com.company.MyApp"
account:account
error:&error];
if (!success) {
NSLog(@"Failed to save: %@", error);
}
return success;
}
Common Use Cases
Storing User Credentials
- (void)saveLoginCredentials:(NSString *)username password:(NSString *)password {
NSString *serviceName = @"com.company.MyApp.login";
NSError *error = nil;
BOOL success = [SAMKeychain setPassword:password
forService:serviceName
account:username
error:&error];
if (success) {
NSLog(@"Credentials saved for %@", username);
} else {
NSLog(@"Failed to save credentials: %@", error.localizedDescription);
}
}
Storing API Tokens
- (void)saveAPIToken:(NSString *)token forService:(NSString *)serviceName {
// Use a standard account name for API tokens
NSString *account = @"api_token";
[SAMKeychain setPassword:token
forService:serviceName
account:account];
}
Storing OAuth Refresh Tokens
- (void)saveOAuthTokens:(NSString *)accessToken
refreshToken:(NSString *)refreshToken
forAccount:(NSString *)account {
NSString *service = @"com.company.MyApp.oauth";
// Store access token
[SAMKeychain setPassword:accessToken
forService:[service stringByAppendingString:@".access"]
account:account];
// Store refresh token
[SAMKeychain setPassword:refreshToken
forService:[service stringByAppendingString:@".refresh"]
account:account];
}
Next Steps
Retrieving Passwords Learn how to safely retrieve stored passwords
Managing Accounts List, update, and delete keychain items
Synchronization Sync passwords across devices with iCloud Keychain
Advanced Queries Use SAMKeychainQuery for complex operations