Communicating with a REST API and saving API token in iOS's keychain. But the keychain code is throwing a nil error.
KeychainAccess.swift:
public class func passwordForAccount(account: String, service: String = "keyChainDefaultService") -> String? {
let queryAttributes = NSDictionary(objects: [secClassGenericPassword(), service, account, true], forKeys: [secClass(), secAttrService(), secAttrAccount(), secReturnData()])
var retrievedData: NSData?
var extractedData: AnyObject?
let status = SecItemCopyMatching(queryAttributes, &extractedData)
if (status == errSecSuccess) {
retrievedData = extractedData as? NSData
}
let password = NSString(data: retrievedData!, encoding: NSUTF8StringEncoding)
return (password as! String)
}
In the above code, retrievedData is nil. If I do print(status), I get -25300. This function is being called from a view controller:
// check if API token has expired
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let userTokenExpiryDate : String? = KeychainAccess.passwordForAccount("Auth_Token_Expiry", service: "KeyChainService")
let dateFromString : NSDate? = dateFormatter.dateFromString(userTokenExpiryDate!)
let now = NSDate()
I am not sure where I'm going wrong here. Any pointers?
var extractedData : AnyObject?;
let status = withUnsafeMutablePointer(&extractedData) {
SecItemCopyMatching(queryAttributes, UnsafeMutablePointer($0))
}
Try doing it like this, this is how I had to get it working
Related
If have the following code, which should transform a [String: any] document from Firestore into a struct.
When I debug at that time all requirements are met but after returning the value is nil.
I tried changing the init? to a regular init and an else { fatalError() } on the guard. This works and returns a valid struct if data is valid.
What am I doing wrong with the failable initializer?
This does not work (always returns nil, even with valid data):
struct Banner {
let destinationUrl: URL
let imageUrl: URL
let endTime: Date
let startTime: Date
let priority: Int
let trackingKeyClicked: String
let trackingKeyDismissed: String
init?(document: [String: Any]) {
guard
let destinationUrlString = document["destinationUrl"] as? String,
let destinationUrl = URL(string: destinationUrlString),
let imageUrlString = document["imageUrl"] as? String,
let imageUrl = URL(string: imageUrlString),
let priority = document["priority"] as? Int,
let trackingKeyClicked = document["trackingKeyClicked"] as? String,
let trackingKeyDismissed = document["trackingKeyDismissed"] as? String,
let startTime = document["startTime"] as? Date,
let endTime = document["endTime"] as? Date
else { return nil }
self.destinationUrl = destinationUrl
self.imageUrl = imageUrl
self.priority = priority
self.trackingKeyClicked = trackingKeyClicked
self.trackingKeyDismissed = trackingKeyDismissed
self.endTime = endTime
self.startTime = startTime
}
}
// using it like this
let bannerStructs = querySnapshot.documents.map { Banner(document: $0.data()) }
This works with valid data (but crashes on wrong data instead of returning nil):
struct Banner {
let destinationUrl: URL
// ...
let endTime: Date
init(document: [String: Any]) {
guard
let destinationUrlString = document["destinationUrl"] as? String,
let destinationUrl = URL(string: destinationUrlString),
// ....
let endTime = document["endTime"] as? Date
else { fatalError() }
self.destinationUrl = destinationUrl
// ...
self.endTime = endTime
}
}
If the failable initialiser is returning nil and the normal initialiser is crashing because of bad data then that points me towards the guard statement in the failable initialiser failing leading to it returning nil. Place a breakpoint on the return nil line within the guard statement and see if this is being hit.
If none of your guard let conditions are met it will fail and eventually trigger a fatalError or return nil, depending on which implementation you use.
Please debug well which of the data is not parsed/casted correctly.
I setup a good example on how you might expect it to work and a bad example to let you know how one attribute that is not expected in that format can make the initialiser return nil:
import Foundation
struct Banner {
let destinationUrl: URL
let imageUrl: URL
let endTime: Date
let startTime: Date
let priority: Int
let trackingKeyClicked: String
let trackingKeyDismissed: String
init?(document: [String: Any]) {
guard
let destinationUrlString = document["destinationUrl"] as? String,
let destinationUrl = URL(string: destinationUrlString),
let imageUrlString = document["imageUrl"] as? String,
let imageUrl = URL(string: imageUrlString),
let priority = document["priority"] as? Int,
let trackingKeyClicked = document["trackingKeyClicked"] as? String,
let trackingKeyDismissed = document["trackingKeyDismissed"] as? String,
let startTime = document["startTime"] as? Date,
let endTime = document["endTime"] as? Date
else { return nil }
self.destinationUrl = destinationUrl
self.imageUrl = imageUrl
self.priority = priority
self.trackingKeyClicked = trackingKeyClicked
self.trackingKeyDismissed = trackingKeyDismissed
self.endTime = endTime
self.startTime = startTime
}
}
// using it like this
let goodData:[String:Any] = [
"destinationUrl": "http://destination--url",
"imageUrl": "http://image-url",
"priority": 17,
"trackingKeyClicked": "Tracking Key Clicked",
"trackingKeyDismissed": "Tracking Key Dismissed",
"startTime": Date(),
"endTime": Date()
]
let goodBannerStructs = Banner(document: goodData)
let badData:[String:Any] = [
"destinationUrl": "http://destination--url",
"imageUrl": "http://image-url",
"priority": 17,
"trackingKeyClicked": "Tracking Key Clicked",
"trackingKeyDismissed": "Tracking Key Dismissed",
"startTime": "17 December",
"endTime": Date()
]
let badBannerStructs = Banner(document: badData)
print("Good banner: \(goodBannerStructs)")
print("Bad banner: \(badBannerStructs)")
This is what it prints out:
Good banner: Optional(Banner(destinationUrl: http://destination--url, imageUrl: http://image-url, endTime: 2020-01-21 17:45:27 +0000, startTime: 2020-01-21 17:45:27 +0000, priority: 17, trackingKeyClicked: "Tracking Key Clicked", trackingKeyDismissed: "Tracking Key Dismissed"))
Bad banner: nil
You can try this code on: http://online.swiftplayground.run/
It can be one the dictionary keys of document might be incorrect with what is coming from the query, might be that priority might not be an Int or the dates might be String. You have to debug it.
This does not work (always returns nil, even with valid data)
Since your guard is always failing, the data seems to be incorrect. I guess that startDate and endDate aren't that easy to be converted to Date. Could you please post a example of the json data?
If this is the cause, here is someone describing how to use a DateFormatter to create a date from a string. If your dates follow ISO8601 you can use Apples ISO8601DateFormatter to do it.
I'm trying to use some keychain code to securely and persistently store some sensitive data. I have functions to save and read data which work fine but I cannot seem to get the update one to work.
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, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
let status = SecItemUpdate(keychainQuery as CFDictionary, [kSecValueDataValue:dataFromString] as CFDictionary)
if (status != errSecSuccess) {
if let err = SecCopyErrorMessageString(status, nil) {
print("Read failed: \(err)")
print("status: \(status)")
}
}
}
}
...
I am constantly getting error status -50 indicating one or more parameters passed to the function were not valid. I tried reading through the docs but couldn't come up with anything that worked.
Thank you in advance!
It turned out that I had to remove the last two keys from the keychainQuery dictionary so it looks like this:
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 != errSecSuccess) {
if let err = SecCopyErrorMessageString(status, nil) {
print("Read failed: \(err)")
print("status: \(status)")
}
}
}
}
...
I want to remove keychain value form my service, when user has logout. However, I feel confused about the best practice way to do that.
Here is my service
let userAccount = "AuthenticatedUser"
let accessGroup = "SecuritySerivice"
let passwordKey = "KeyForPassword"
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 {
public class func savePassword(token: String) {
self.save(service: passwordKey, data: token)
}
public class func loadPassword() -> String? {
return self.load(service: passwordKey)
}
public class func removePassword() {
}
private class func save(service: String, data: String) {
let dataFromString: Data = data.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)!
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])
SecItemDelete(keychainQuery as CFDictionary)
SecItemAdd(keychainQuery as CFDictionary, nil)
}
private class func load(service: String) -> String? {
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
var dataTypeRef :AnyObject?
// Search for the keychain items
let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
var contentsOfKeychain: String? = nil
if status == errSecSuccess {
if let retrievedData = dataTypeRef as? Data {
contentsOfKeychain = String(data: retrievedData, encoding: String.Encoding.utf8)
}
} else {
print("KEY: Nothing was retrieved from the keychain. Status code \(status)")
}
return contentsOfKeychain
}
private class func remove(service: String) {
}
}
Obviously, I'm not shure whether I should remove anything form keychain actually
If you want to delete the item using KeychainItemWrapper, use -resetKeychainItem. This calls SecItemDelete() with the correct value.
To remove username password in Keychain use
func removeUserFromKeychain() {
let spec: NSDictionary = [kSecClass: kSecClassGenericPassword]
SecItemDelete(spec)
}
You can clear your data. Please try below lines
keychain["yourKey"] = nil
or
do {
try keychain.remove("yourKey")
} catch let error {
print("error: \(error)")
}
Thanks
This question already has answers here:
Save and retrieve value via KeyChain
(7 answers)
Closed 6 years ago.
How to simply store a String in Keychain and load when needed.
There are several SO solution which mostly refers to Git repo. But I need the smallest and the simplest solution on latest Swift. Certainly, I don't want to add git framework for simply storing a password in my project.
There are similar solution Save and retrieve value via KeyChain , which did not work for me. Tired with compiler errors.
Simplest Source
import Foundation
import Security
// Constant Identifiers
let userAccount = "AuthenticatedUser"
let accessGroup = "SecuritySerivice"
/**
* User defined keys for new entry
* Note: add new keys for new secure item and use them in load and save methods
*/
let passwordKey = "KeyForPassword"
// 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 {
/**
* Exposed methods to perform save and load queries.
*/
public class func savePassword(token: NSString) {
self.save(passwordKey, data: token)
}
public class func loadPassword() -> NSString? {
return self.load(passwordKey)
}
/**
* Internal methods for querying the keychain.
*/
private class func save(service: NSString, data: NSString) {
let dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
// Instantiate a new default keychain query
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])
// Delete any existing items
SecItemDelete(keychainQuery as CFDictionaryRef)
// Add the new keychain item
SecItemAdd(keychainQuery as CFDictionaryRef, nil)
}
private class func load(service: NSString) -> NSString? {
// 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, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
var dataTypeRef :AnyObject?
// Search for the keychain items
let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
var contentsOfKeychain: NSString? = nil
if status == errSecSuccess {
if let retrievedData = dataTypeRef as? NSData {
contentsOfKeychain = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
}
} else {
print("Nothing was retrieved from the keychain. Status code \(status)")
}
return contentsOfKeychain
}
}
Example of Calling
KeychainService.savePassword("Pa55worD")
let password = KeychainService.loadPassword() // password = "Pa55worD"
SWIFT 4: VERSION WITH UPDATE AND REMOVE PASSWORD
import Cocoa
import Security
// see https://stackoverflow.com/a/37539998/1694526
// 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 != errSecSuccess) {
if let err = SecCopyErrorMessageString(status, nil) {
print("Read failed: \(err)")
}
}
}
}
class func removePassword(service: String, account:String) {
// Instantiate a new default keychain query
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, kCFBooleanTrue], 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)
if (status != errSecSuccess) { // Always check the status
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, 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)
}
} else {
print("Nothing was retrieved from the keychain. Status code \(status)")
}
return contentsOfKeychain
}
}
You need to imagine the following wired up to a text input field and a label, then having four buttons wired up, one for each of the methods.
class ViewController: NSViewController {
#IBOutlet weak var enterPassword: NSTextField!
#IBOutlet weak var retrievedPassword: NSTextField!
let service = "myService"
let account = "myAccount"
// will only work after
#IBAction func updatePassword(_ sender: Any) {
KeychainService.updatePassword(service: service, account: account, data: enterPassword.stringValue)
}
#IBAction func removePassword(_ sender: Any) {
KeychainService.removePassword(service: service, account: account)
}
#IBAction func passwordSet(_ sender: Any) {
let password = enterPassword.stringValue
KeychainService.savePassword(service: service, account: account, data: password)
}
#IBAction func passwordGet(_ sender: Any) {
if let str = KeychainService.loadPassword(service: service, account: account) {
retrievedPassword.stringValue = str
}
else {retrievedPassword.stringValue = "Password does not exist" }
}
}
Swift 5
Kosuke's version for Swift 5
import Security
import UIKit
class KeyChain {
class func save(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ] as [String : Any]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil)
}
class func load(key: String) -> Data? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue!,
kSecMatchLimit as String : kSecMatchLimitOne ] as [String : Any]
var dataTypeRef: AnyObject? = nil
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == noErr {
return dataTypeRef as! Data?
} else {
return nil
}
}
class func createUniqueID() -> String {
let uuid: CFUUID = CFUUIDCreate(nil)
let cfStr: CFString = CFUUIDCreateString(nil, uuid)
let swiftString: String = cfStr as String
return swiftString
}
}
extension Data {
init<T>(from value: T) {
var value = value
self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func to<T>(type: T.Type) -> T {
return self.withUnsafeBytes { $0.load(as: T.self) }
}
}
Example usage:
let int: Int = 555
let data = Data(from: int)
let status = KeyChain.save(key: "MyNumber", data: data)
print("status: ", status)
if let receivedData = KeyChain.load(key: "MyNumber") {
let result = receivedData.to(type: Int.self)
print("result: ", result)
}
I'm trying to use Apple's keychain service based on a tutorial I found (https://www.raywenderlich.com/85528/user-accounts-ios-ruby-rails-swift#next_section). However I am shown this error
Cannot convert value of type 'Unmanaged< AnyObject>'?' to expected argument type 'AnyObject?'
At this block of codes
var dataTypeRef : Unmanaged<AnyObject>?
let status = SecItemCopyMatching(queryAttributes, &dataTypeRef)
if dataTypeRef == nil {
return nil
}
The full code block is as shown below
public class func passwordForAccount(account: String, service: String = "keyChainDefaultService") -> String? {
let queryAttributes = NSDictionary(objects: [secClassGenericPassword(), service, account, true], forKeys: [secClass(), secAttrService(), secAttrAccount(), secReturnData()])
var dataTypeRef : Unmanaged<AnyObject>?
let status = SecItemCopyMatching(queryAttributes, &dataTypeRef)
if dataTypeRef == nil {
return nil
}
let retrievedData : NSData = dataTypeRef!.takeRetainedValue() as! NSData
let password = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
return (password as! String)
}
The entire code can be found here (https://codeshare.io/426px) at line 52- 57.