How to set cookies for UIWebView after loaded? - ios

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];

Related

NotificationHub Register issue at IOS (React Native)

I'm trying to register notificationHub from React Native. I created a NativeModule and I'm sending connectionString, hubName, tags and deviceToken - deviceToken was saved to AsyncStorage (I'm getting deviceToken before user logged and I can't register Notification Hub without user tag (ex: scholarshipID) so I'm saving deviceToken for later usage. Because I can't send user tags to didRegisterForRemoteNotificationsWithDeviceToken from RN )
After user login I'm getting user scholarshipID & deviceToken and sending to native module for NotificationHub register.
// deviceToken:(NSString *)tokenForLaterUsage
[hub
registerNativeWithDeviceToken:tokenForLaterUsage
tags:self->_tags
completion:^(NSError* error)
{
if (error != nil) {
NSLog(#"Error registering for notifications: %#", error);
}else{
NSLog(#"Success");
}
}];
My tokenForLaterUsage is string and if I try use directly app is crashing. Because NotificationHub register function is waiting NSData so I'm converting NSString to NSData like this way:
NSData* data = [tokenForLaterUsage dataUsingEncoding:NSUTF8StringEncoding];
After this I'm going Azure Portal and trying to send notification but system saying no device found.
BTW : When I try to register to NotificationHub in here: didRegisterForRemoteNotificationsWithDeviceToken it's work well but I can't set tags so this is not for me. From here I'm just getting deviceToken for later usage.
--
note: I'm thinking 2 workaround to solve this issue (But I'm not sure if works.):
Workaround: I don't know but if I can I'll not register at first and when user will be login I'll get deviceToken on IOS and I'll try to register to NotificationHub.
Workaround: I'll send my deviceToken to directly backend with restApi and backEnd will pair user tags and deviceToken.
Sorry things mixed but I need to solve this. Also sorry for my English :).
Finally I found the answer.
At first when deviceToken is generated I'm storing at NSUserDefaults
NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults];
[userPreferences setObject:deviceToken forKey:#"deviceToken"];
[userPreferences synchronize];
And when user login I'm getting the deviceToken from NSUserDefault in NativeModules and sending to NotificationHub.
NSData *token = [[NSUserDefaults standardUserDefaults] objectForKey:#"deviceToken"];

ios storing login token in KeyChain fails to retrieve, rarely and randomly, but consistently

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.

Instagram: How to get a brand new token for another user

I am working on an iOS app that uses Instagram Integration. I am able to have everything working. The on thing I just noticed though, once I've called:
https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token
and received my token, let's say another user wants to login using the same device, I will need another token for that user. The problem I am having is that when I call https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token subsequent times, it is not bringing up the IG login screen. It automatically authenticates the existing token and redirects to the user page.
Is there soem kind of parameter that can be included in the URL call to force instagram to bring up the login screen, so another user can authenticate?
Deleting cookies will log the user out. It looks like you are asking for iOS:
NSHTTPCookie *cookie;
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (cookie in [storage cookies]) {
[storage deleteCookie:cookie];
}
The previous answer should work for you. You just need to add the following statement at the end.
[[NSUserDefaults standardUserDefaults] synchronize];
Here is the full Swift code for deleting all cookies:
let storage = NSHTTPCookieStorage.sharedHTTPCookieStorage()
if let cookies = storage.cookies {
for cookie in cookies as [NSHTTPCookie] {
storage.deleteCookie(cookie)
}
}
NSUserDefaults.standardUserDefaults().synchronize()

How to remember username for next time on iPhone app?

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.

AFNetworking and Cookies

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.

Resources