I am working on an iPhone/iOS app. It has a login page with two text fields "email" & "Password".Now i want to save email.
i.e When I logged out it should remember the email address so it doesn't have to be input again.
Please tell me how can I save email.
If you don't consider email sensitive. Use NSUserDefaults...
Saving
[[NSUserDefaults standardUserDefaults]setObject:#"example#email.com" forKey:#"email"];
[[NSUserDefaults standardUserDefaults]synchronize];
Retrieving
// getting an NSString
NSString *emailString = [[NSUserDefaults standardUserDefaults] stringForKey:#"email"];\
IMPROVE ANSWER
If you consider email sensitive. Use keychain instead.
To store
KeychainItemWrapper *keychain =
[[KeychainItemWrapper alloc] initWithIdentifier:#"MyAppLoginData" accessGroup:nil];
[keychain setObject:loginStr forKey:(id)kSecAttrAccount];
[keychain setObject:pwdStr forKey:(id)kSecValueData];
To query
NSString *login = [keychain objectForKey:(id)kSecAttrAccount];
NSString *pwd = [keychain objectForKey:(id)kSecValueData];
To Delete:
[keychain resetKeychainItem];
To do this you will first need to add KeychainItemWrapper in you project.
Another important aspects of using keychain to store data is
The data is persistent even after app uninstall-install
Data can be shared across apps too. Read more here
In this case email is a part of user credentials, needed to login. So i would say it is sensitive information. Please do not ever use NSUserDefaults for storing sensitive data, that belongs to user. These defaults are stored as plist in binary format, they are not encrypted. Use Keychain Services API
provided by iOS instead.
Related
Here is the situation:
there is a webView in my native app, and some operations need to interact with native app.
e.g.: The follow operation, if user has logged in when follow, then everything goes fine.(the webView knows there is a user logged in according to the existed cookies)
but if user has not logged in yet when follow, then it will present the login View Controller(which is native), and after user finish logging in(will response with the new cookie that mark user as logged-in),it's expected that follow operation will continue automatically.
The problem is it seems like the webView doesn't know user has logged in without reloading.
Thanks for any suggestions or corrections.
You can save the cookies using this :
NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject: [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject: cookiesData forKey: #"cookies"];
[defaults synchronize];
I'm using the ios keychain (keychainItemWrapper / SSKeychain) to store my app's login token and maintain logged in state. Currently I store a simple NSDictionary in the keychain containing my token, a token expiry and a refresh token. I serialize it to NSData and storing using kSecValueData. I also set the kSecAttrAccount and kSecAttrService, but don't use those for auth.
This works great, about 95% of the time. The problem is that that randomly, unpredictably and sporadically, the keychain does not return data when I request it to retrieve the token. It is usually after a moderate time away from the app, when reopening it. It doesn't have to be from in background, or after any specific delay though.
It fails specifically when asking for my NSData below and returns <> instead of <ABCD EFGH IJKL ....>. I think it is nil. Thus the code thinks the user isn't logged in and drops them immediately on my App's Signup/Login landing page, without logout error, token expiry error, etc. If I minimize the app, then reopen, it almost always gets the correct keychain info and the user is logged in again.
This creates a confusing experience when encountered. It also means the user can't maintain this true 100% logged in state, with occasionally being randomly logged out. I've been unable to predict it or debug it and changing keychain libraries, as shown below, hasn't fixed it for me. It happens for me, and several TestFlight users, and in our production app currently.
Any suggestions how to maintain keychain integrity and loading 100% of time? We're about ready to implement an NSUserDefaults backup storage on the token to use in these cases, something I really don't want to do to store an auth token.
Storing:
// load keychain
KeychainItemWrapper *keychainItem = [KeychainItemWrapper keyChainWrapperForKeyID:kcIdentifier];
NSString *firstLaunch = [keychainItem objectForKey: (__bridge id)(kSecAttrAccount)];
if (firstLaunch == nil){
// initialize if needed
[keychainItem setObject:email forKey: (__bridge id)(kSecAttrAccount)];
[keychainItem setObject:kcIdentifier forKey: (__bridge id)kSecAttrService];
[keychainItem setObject:(id)kSecAttrAccessibleAfterFirstUnlock forKey:(id)kSecAttrAccessible];
}
// serialize "auth" NSDictionary into NSData and store
NSString *error;
NSData *dictionaryData = [NSPropertyListSerialization dataFromPropertyList:auth format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
[keychainItem setObject:dictionaryData forKey:(id)kSecValueData];
Loading:
// after similar KeychainItemWrapper initialization as above
NSData *dictionaryData = [keychainItem objectForKey:(id)kSecValueData];
NSString *error;
NSDictionary *auth = [NSPropertyListSerialization propertyListFromData:dictionaryData mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error];
NSString *token = auth[#"access_token"];
I have also tried using the SSKeychain library CocoaPod that is widely available, and a wrapper around the keychain logic. It is a cleaner access but fails with the same issue. Here I'm just storing NSString values since there was no direct way to store NSData in the lib.
// store in keychain
[SSKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlock];
[SSKeychain setPassword:auth[#"access_token"] forService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_TOKEN];
[SSKeychain setPassword:auth[#"expires_at"] forService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_EXPIRES_AT];
[SSKeychain setPassword:auth[#"refresh_token"] forService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_REFRESH_TOKEN];
// load from keychain
[SSKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlock];
NSString *token = [SSKeychain passwordForService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_TOKEN];
NSString *expires_at = [SSKeychain passwordForService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_EXPIRES_AT];
NSString *refresh_token = [SSKeychain passwordForService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_REFRESH_TOKEN];
Keychain does have issues at the moment, and for quite a while really. It sounds like you're getting off lightly as usually when it breaks a force-quit of the app is required to bring it back to life.
One thing that helps is to access the keychain just once on the first request and then cache the result in memory, if it's already in memory then just return it from there.
If you can observe a specific error when this happens then trap it and retry or, as is the current case for some unfortunate apps, kill the app. Killing the app is actually the current guidance from Apple if you raise a tech ticket to discuss the issue with them.
The only other real solution is to encrypt the data and store it in a file, but then you have issues with encryption keys so this is little better than obfuscation against a keen attacker.
I use Facebook login with my app. (Facebook iOS SDK version 3.11.1)
I ask for "email" permission:
NSArray *permissions = [NSArray arrayWithObjects: #"basic_info", #"email", nil];
Most of the time, I do get the user's email like that:
NSString *email = [user objectForKey:#"email"];
//user is (NSDictionary<FBGraphUser> *)
Sometimes I just don't. (I can see that the NSDictionary is not including email).
I am using this fix so the app won't terminate when i use email later and its nil:
NSString *email = [user objectForKey:#"email"] ? [user objectForKey:#"email"] : #"NO_EMAIL";
But i need the real mail, so i have to come up with a new solution.
I haven't noticed something special with the problematic users.
Any ideas what can be the problem?
It appears that its a well known problem... For many reasons, not everyone on Facebook have Email address registered.
But as i said, my problem is that i need a real mail.
So the simplest solution is to use the user's Facebook Email : user_name#facebook.com
NSString *email = [user objectForKey:#"email"] ? [user objectForKey:#"email"] : [NSString stringWithFormat:#"%##facebook.com", user.username];
some links ref to that you can't sent HTML emails to the Facebook mail, only plain text!
Here is how i have done it and it always works great for me in every situation:
(I suppose that you have added email permission in your code)
Step 1
Open Facebook Framework folder in Xcode and find the FBGraphUser.h class
As you see, you have there all the properties that you use from Facebook Framework, to take the user details, so add an other property there (copy and paste the code below):
Step 2
#property (retain, nonatomic) id<FBGraphUser> email;
And you are good to go!
You could check for a couple of things:
Make sure you are requesting email scope permission in the login request dialog:
https://www.facebook.com/dialog/oauth?client_id=APP&redirect_uri=REDIRECT&state=UNIQUE&scope=email
Check if the access token you got actually has email permission granted with the Access Token Debugger.
All facebook accounts which are verified through Phone Number instead of email will get NULL for [user objectForKey:#"email"]
Facebook won't return the email address if it's not verified.
https://developers.facebook.com/docs/graph-api/reference/v2.2/user
This person's primary email address listed on their profile. This field will not be returned if no valid email address is available.
i have a question about saving Keys within iOS applications.
Is there any way to save the public/private Key of an RSA-certificate within an app, not to save/store this in the iOS keychain.
Maybe someone tried this out before?
I found something similar with username and password. Its called "Keychainwrapper". There the app saves the login credentials within the app. Does this work for me with private/public keys?
Hopefully someone can help me.
Best regards,
Andi
Since you have to store only public and private key and not the whole certificate, can not use the internal keychain and the keys are generated on the device it would be sufficient if you save your public and private key in the NSUserDefaults:
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
//saving the data
[prefs setObject"<your public key data>" forKey:#"PublicKey"];
[prefs setObject"<your private key data>" forKey:#"PrivateKey"];
//reading the data
NSString *publicKey = [prefs stringForKey:#"PublicKey"];
NSString *privateKey = [prefs stringForKey:#"PrivateKey"];
This store is rather insecure, so I would suggest that you encrypt your data like this: iOS 5: Data encryption AES-256 EncryptWithKey: not found. Be aware, that symmetric encryption is not secure if you store the key in your application! You should use some user input to generate the key.
Hope this helps.
I'm using AFNetworking as a network layer for my iPhone app which connects to a Rails server that uses Devise for authentication. If I sign in (with a POST call) providing username/password then after that any GET I perform is ok.
If I close the app (not just background) then all my GET requests fail because I guess they're not authenticated.
So I presume cookies are stored somewhere; is there a way to save them in NSUserDefaults or somewhere like that in order to avoid logging in all the time?
You do not need to bother with NSUserDefaults nor any keychain wrapper if you use NSURLCredential.
Indeed NSURLCredential is much simpler to use, as it allows you to store both username and password in the keychain in two lines of code.
Your code would be something like that once the user is logged in:
NSURLCredential *credential;
credential = [NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistencePermanent];
[[NSURLCredentialStorage sharedCredentialStorage] setCredential:credential forProtectionSpace:self.loginProtectionSpace];
Then, each time the app is launched, you can check whether your user was already logged in by searching for any credential in order to automatically log back your user (if need be):
NSURLCredential *credential;
NSDictionary *credentials;
credentials = [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:self.loginProtectionSpace];
credential = [credentials.objectEnumerator nextObject];
NSLog(#"User %# already connected with password %#", credential.user, credential.password);
You also need to clean the credential when the user wants to log out:
NSURLCredential *credential;
NSDictionary *credentials;
credentials = [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:self.loginProtectionSpace];
credential = [credentials.objectEnumerator nextObject];
[[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:self.loginProtectionSpace];
loginProtectionSpace is created once for all. Please, note this sample code assumes there is only one credential in this space, which is usually the case unless you manage several accounts.
Here is an example of how you would create a NSURLProtectionSpace:
NSURL *url = [NSURL URLWithString:#"http://www.example.com"];
self.loginProtectionSpace = [[NSURLProtectionSpace alloc] initWithHost:url.host
port:[url.port integerValue]
protocol:url.scheme
realm:nil
authenticationMethod:NSURLAuthenticationMethodHTTPDigest];
Cookies are indeed automatically stored for the lifetime of your application for any subsequent requests on a particular server. A good strategy would be to store the username and password in the keychain or in NSUserDefaults like this:
// Setting
[[NSUserDefaults standardDefaults] setObject:username forKey:#"username"];
[[NSUserDefaults standardDefaults] synchronize];
// Getting
NSString *username = [[NSUserDefaults standardDefaults] objectForKey:#"username"];
You may want to use this in combination with AFHTTPClient to send your credentials along with every request in an Authorization HTTP header.