As @MobileTen stated, never store passwords in UserDefaults. You need to use the Keychain.
Also, I'm not sure why you need to store something called passcode-reset and passcode-set. The very fact that a password exists means it has been set, and if you're resetting a passcode, then that should be done as part of the journey for the user, not something that needs storing anywhere. In other words, if the user wants to reset their password, ask them for a new one, and save the new password to the keychain. There's no need to store in UDs that you're resetting the password.
So, here's how to use the keychain:
let userAccount: String = "AuthenticatedUser"
let passwordKey: String = "SomeSortOfStringIdentifyingThisIsAPasswordForYourApp" // Choose a relevant value and never change it, as the password is stored against this value
func getKeychainPasscode() -> String {
return KeychainService.loadPassword(service: passwordKey, account: userAccount) ?? ""
}
// getKeychainPasscode() will tell you if a password is set. If it returns "" then it hasn't been set.
func updatePasscode(_ value: String) {
KeychainService.updatePassword(service: passwordKey, account: userAccount, data: value)
}
// Arguments for the keychain queries
let kSecClassValue = NSString(format: kSecClass)
let kSecAttrAccountValue = NSString(format: kSecAttrAccount)
let kSecValueDataValue = NSString(format: kSecValueData)
let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword)
let kSecAttrServiceValue = NSString(format: kSecAttrService)
let kSecMatchLimitValue = NSString(format: kSecMatchLimit)
let kSecReturnDataValue = NSString(format: kSecReturnData)
let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne)
public class KeychainService: NSObject {
class func updatePassword(service: String, account: String, data: String) {
if let dataFromString: Data = data.data(using: String.Encoding.utf8, allowLossyConversion: false) {
// Instantiate a new default keychain query
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue])
let status = SecItemUpdate(keychainQuery as CFDictionary, [kSecValueDataValue : dataFromString] as CFDictionary)
if(status == errSecItemNotFound) {
// No existing passcode, so just save the new one
savePassword(service: service, account: account, data: data)
} else {
// Passcode exists, so delete it and save the new one
removePassword(service: service, account: account)
savePassword(service: service, account: account, data: data)
}
}
}
class func removePassword(service: String, account: String) {
// Instantiate a new default keychain query
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, kCFBooleanTrue ?? true], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue])
// Delete any existing items
let status = SecItemDelete(keychainQuery as CFDictionary)
if(status != errSecSuccess) {
if let err = SecCopyErrorMessageString(status, nil) {
print("Remove failed: \(err)")
}
}
}
class func savePassword(service: String, account: String, data: String) {
if let dataFromString = data.data(using: String.Encoding.utf8, allowLossyConversion: false) {
// Instantiate a new default keychain query
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])
// Add the new keychain item
let status = SecItemAdd(keychainQuery as CFDictionary, nil)
// Always check the status
if (status != errSecSuccess) {
if let err = SecCopyErrorMessageString(status, nil) {
print("Write failed: \(err)")
}
}
}
}
class func loadPassword(service: String, account: String) -> String? {
// Instantiate a new default keychain query
// Tell the query to return a result
// Limit our results to one item
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, kCFBooleanTrue ?? true, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
var dataTypeRef: AnyObject?
// Search for the keychain items
let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
var contentsOfKeychain: String?
if(status == errSecSuccess) {
if let retrievedData = dataTypeRef as? Data {
contentsOfKeychain = String(data: retrievedData, encoding: String.Encoding.utf8)
}
}
return contentsOfKeychain
}
}