How can I use keychain when I close my ios app? - ios

I'm developing an iOS app and want user only type username:password pair only once (unless he logs out). Currently I use keychain-swift framework for storing emails/passwords.
I basically run:
let keychain = KeychainSwift()
keychain.set("johndoe", forKey: "my key") // on a successful login
keychain.get("my key")
When I run my app in a simulator I have to type password all the time (i.e., it doesn't look like it saves the password in keychain between the sessions).
Is it expected? What framework will allow me to save the data even when I close the app such that a user won't have to type username:password pairs every time to sign in?

I have never used KeychainSwift but at a guess you could do something like:
let keychain = KeychainSwift(keyPrefix: "com.Daniel.myIOSapp.")
keychain.set("johndoe", forKey: "username")
keychain.set("where is jane", forKey: "password")
which will create two "generic password" keychain items com.Daniel.myIOSapp.username and com.Daniel.myIOSapp.password and the associated values.
You normally store the username/password pair as a single keychain item. You can do that with KeychainSwift using something like:
keychain.set("where is jane", forKey: "johndoe")
which creates a single generic password item in the keychain and you probably want to store "johndoe" in your preferences under a suitable key.
HTH

Haven't used this framework myself, but from looking at the code it appears that it will save, and therefore be accessible after app is restarted.
You might want to confirm the set function is working properly. From the readme here:
https://github.com/evgenyneu/keychain-swift/blob/master/README.md
...it states:
Check if operation was successful
One can verify if set, delete and clear methods finished successfully
by checking their return values. Those methods return true on success
and false on error.
if keychain.set("hello world", forKey: "my key") { // Keychain item
is saved successfully } else { // Report error }
Also, just to confirm, the get function should read:
let myKey = keychain.get("my key")
...where myKey is put into your text field(s).

Related

AWS Cognito check and get users

I'm building an iOS App that is using Amazon MobileHub. Currently it's associated with Cognito and the sign up and sign in flow works great.
What I'm trying to achieve is for one user to be able to add another user as a friend so to speak. To make that possible, I need to check if a user with a specific username exists and if it does, get some attributes such as the name of that target user.
I've tried to use the get user > get details function but it gives me an error Authentication delegate not set.
Here's the code I used:
var pool: AWSCognitoIdentityUserPool?
let user = pool?.getUser(usernameField.text!)
self.pool = AWSCognitoIdentityUserPool.init(forKey: AWSCognitoUserPoolsSignInProviderKey)
user?.getDetails().continueWith { (task: AWSTask<AWSCognitoIdentityUserGetDetailsResponse>) -> Any? in
if let error = task.error as NSError? {
print("ERROR: " + error.localizedDescription)
return ""
}
print(task.result)
return ""
}
An approach I thought of was to store the username and the attributes I want to access to DynamoDB and then access it there but that will just create double unnecessary entries.
The issue you'll run into is that user attributes aren't publicly visible. Only the user who has signed in can call GetUser. If it's a back end process, you could do this via the AdminGetUser operation, but this looks client side so I wouldn't recommend that. The only real way around this would be to do what you suggested at the bottom of your post, ultimately.

iOS - AWS Cognito - Check if user already exists

I want to allow a user to enter their email address/password in a field. Upon continuing, I want to run a check to see if that user already exists. If they do, log them in and continue with app, if they do not, move to account creation flow where they will be instructed to add name, phone number, etc.
I cannot for the life of me find documentation on how to log a user in using AWS Cognito. I should be able to pass email/pass in a call and get a response back that says User Exists/User does not exist or whatever! Am I missing something here?
Any help would be greatly appreciated. I've scoured the documentation..this is my last resort.
In the current SDK, calling getUser on your AWSCognitoIdentityUserPool just constructs the in-memory user object. To make the call over the network, you need to call the getSession method on the constructed user. Here's a Swift 3 method I wrote to check whether an email is available:
/// Check whether an email address is available.
///
/// - Parameters:
/// - email: Check whether this email is available.
/// - completion: Called on completion with parameter true if email is available, and false otherwise.
func checkEmail(_ email: String, completion: #escaping (Bool) -> Void) {
let proposedUser = CognitoIdentityUserPoolManager.shared.pool.getUser(email)
UIApplication.shared.isNetworkActivityIndicatorVisible = true
proposedUser.getSession(email, password: "deadbeef", validationData: nil).continueWith(executor: AWSExecutor.mainThread(), block: { (awsTask) in
UIApplication.shared.isNetworkActivityIndicatorVisible = false
if let error = awsTask.error as? NSError {
// Error implies login failed. Check reason for failure
let exceptionString = error.userInfo["__type"] as! String
if let exception = AWSConstants.ExceptionString(rawValue: exceptionString) {
switch exception {
case .notAuthorizedException, .resourceConflictException:
// Account with this email does exist.
completion(false)
default:
// Some other exception (e.g., UserNotFoundException). Allow user to proceed.
completion(true)
}
} else {
// Some error we did not recognize. Optimistically allow user to proceed.
completion(true)
}
} else {
// No error implies login worked (edge case where proposed email
// is linked with an account which has password 'deadbeef').
completion(false)
}
return nil
})
}
For reference, my ExceptionString enum looks like this:
public enum ExceptionString: String {
/// Thrown during sign-up when email is already taken.
case aliasExistsException = "AliasExistsException"
/// Thrown when a user is not authorized to access the requested resource.
case notAuthorizedException = "NotAuthorizedException"
/// Thrown when the requested resource (for example, a dataset or record) does not exist.
case resourceNotFoundException = "ResourceNotFoundException"
/// Thrown when a user tries to use a login which is already linked to another account.
case resourceConflictException = "ResourceConflictException"
/// Thrown for missing or bad input parameter(s).
case invalidParameterException = "InvalidParameterException"
/// Thrown during sign-up when username is taken.
case usernameExistsException = "UsernameExistsException"
/// Thrown when user has not confirmed his email address.
case userNotConfirmedException = "UserNotConfirmedException"
/// Thrown when specified user does not exist.
case userNotFoundException = "UserNotFoundException"
}
Some clarification is in order. Cognito has several parts. The part that does "Authentication" (which is what you are talking about) is called "Cognito User Pools". Not to be confused with Cognito Federated Identity Pools.
With User Pools you can create usernames and password combinations with attributes, and these can be used to authenticate and deliver a persistent, cross device, Cognito Federated identity identityId to a user (across multiple devices).
Once logged in, the Federated Identity Pool is hooked to roles which can get your "Authorized" to use AWS services (like Dynamo DB etc).
It can be tricky to get all these parts working together and AWS has an online site called "Mobile Hub" that will build code for you and download an xcode project. This process configures the Federated Identity Pool and the User Pool correctly, and connects them all up to a set of example code.
Connecting the credentials provider to the user pool to the identity pool is a bit counterintuitive, but the AWSIdentityManager in the aws-mobilehub-helper-ios on github manages all that for you. So I would recommend starting with mobile hub on the console.
Cognito is a somewhat confusing system, here is a link to a brief powerpoint that hits the highlights of how it works (for people that can't understand the AWS docs (like me)).
With that said, "how to check if a user already exists?"
The most reasonable approach is to create the user (via signup), and get a reject if the name is in use, and suggest that your user try a different username. With respect to the email being in use, you will get that reject upon confirmation (signup sends confirmation id's by email and/or via text). This can be overridden to reclaim the email address, or you can do a test beforehand to see if the email is in use by attempting to log in and looking at the failure code.
you can fetch the user as the other answer suggests, however if you have established in user pools an alias for login (like email) you will find this problematic, because this just tells you if someone has the user name, not if someone is already using the email address, and you will get a reject later at confirmation time.
ListUsers is now a nice way to check for existing usernames.
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ListUsers.html
You can also look for existing emails, phone numbers, and other default attributes.
Here is a simple .NET example:
Dim userRequest = New ListUsersRequest With {
.UserPoolId = "poolId",
.Filter = "username = bob#email.com"
}
Dim response = amazonCognitoIdentityProviderClient.ListUsers(userRequest)
Debug.WriteLine(response.Users.Count)

How does this function/variable work?

I'm using the following code to retrieve NSURLCredentials:
let credentials: NSURLCredential? = {
let loginProtectionSpace = NSURLProtectionSpace(host: host, port: 0, protocol: NSURLProtectionSpaceHTTP, realm: nil, authenticationMethod: NSURLAuthenticationMethodDefault)
let credentials = NSURLCredentialStorage.sharedCredentialStorage().defaultCredentialForProtectionSpace(loginProtectionSpace)
return credentials
}()
This is called when the user opens the app. The credentials returned are nil. Then I set these credentials and try to print credentials out again and it's still nil. However, if I restart the app, the printed credentials are there. What's going on here?
This is a lazy variable. The code gets executed once when you first access the property. After that the initially returned value is "remembered" and returned on future calls.
If you set the credentials yourself in the NSURLCredentialStorage then on the next start of the app the first access of the property once again executes the code and retrieves the stored credentials. Note that during the run where you first set the credentials up the actual 3 lines of code retrieving the credentials from the storage are not executed a second time and therefore during that run of the app the property still is nil while there actually is a value in the storage. A similar thing will happen if you modify the existing credentials - during the run where you change them, the credentials will still hold a reference to the previous ones.
If you want to be able to requery the store, you should either
create a function for this instead of a lazy variable
or take a look at this answer: Re-initialize a lazy initialized variable in Swift
I suspect it's because credentials is immutable. The code block gets executed once to assign credentials its value. Which is why it has the credentials after you restarted the app.

Authentication with iOS/Swift

I have a very simple question, I have a node.js/express server that will handle backend authentication part, it is using token not cookies, the server part is working correctly, whenever someone register/login it would return with a JSON web token.
For example:
{
"token" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdW"
}
I'm using Alamofire to handle the HTTP request from iOS itself. The real question lies is how do I persist the token in the iOS/Swift itself?
What is the simplest way to do this?
You should use the iOS Keychain to save sensitive information.
You should not use NSUserDefaults to store an authentication token or any other potentially sensitive information. It's unencrypted and easily accessible on a rooted device.
How would you like someone getting your authentication token and making requests to your private API at will (e.g. on the command line using curl)?
I've used the KeychainAccess CocoaPod and its usage is simple:
static let keychain = Keychain(service: "com.example.myapp")
keychain["secret_code"] = secretCode // Save something
let secretCode = keychain["secret_code"] // Retrieve something
The simplest way is to store it in NSUserDefaults like this:
Writing:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject("Your Variable Value", forKey: "token")
Reading:
let defaults = NSUserDefaults.standardUserDefaults()
if let token = defaults.stringForKey("token") {
print(name)
}
The simplest way may be NSUserDefaults, but the most secure way would be to store the token in the iOS Keychain. Note there are several wrapper libraries (for example) available to make working with the keychain easier in Swift. The API can be a bit intimidating at times.

Is is normal to end up with a LOT of changes on CKFetchRecordChangesOperation?

I use CloudKit in my app, which seems to be working well. However when I initialise the app on a new device, using
CKFetchRecordChangesOperation *fetchRecordChangesOperation = [[CKFetchRecordChangesOperation alloc] initWithRecordZoneID:zoneID previousServerChangeToken:NIL];
I get a LOT of changes, as all previous deletions and changes appear to be synched across.
Is there a better way? for example to just download a full set of current data and set the serverChangeToken to the current value.
When looking at the documentation of the CKServerChangeToken it looks like you can only get it when using the CKFetchRecordChangesOperation. See: https://developer.apple.com/library/prerelease/ios/documentation/CloudKit/Reference/CKServerChangeToken_class/index.html
So now how could you get a hold on that token:
Usually you will do something like this: As you can see in the CKFetchRecordChangesOperation you can initialize it with a previousServerChangeToken. This token works like a timestamp. When the operation completes, you get this token back in the fetchRecordChangesCompletionBlock. You have to save that token in for instance the user defaults. Then the next time you start a CKFetchRecordChangesOperation, you can use that token to start reading the changes since the last time you called it.
Actually saving the token can be a little tricky. I can suggest adding a property like this:
private var previousChangeToken: CKServerChangeToken? {
get {
let encodedObjectData = NSUserDefaults.standardUserDefaults().objectForKey("\(container.containerIdentifier)_lastFetchNotificationId") as? NSData
var decodedData: CKServerChangeToken? = nil
if encodedObjectData != nil {
decodedData = NSKeyedUnarchiver.unarchiveObjectWithData(encodedObjectData!) as? CKServerChangeToken
}
return decodedData
}
set(newToken) {
if newToken != nil {
NSUserDefaults.standardUserDefaults().setObject(NSKeyedArchiver.archivedDataWithRootObject(newToken!), forKey:"\(container.containerIdentifier)_lastFetchNotificationId")
}
}
}
In your case you want a new application to start with a change token that could only have been created by an other running app. So besides saving the change token to the NSUserDefaults, you should also save it in CloudKit in the public Database in one specific settings recordType record. A newly installed app that does not have a token in it's NSUserDefaults can then read the token from your CloudKit settings record.

Resources