I have a client-server application that has a server singleton in it that is a subclass of AFHTTPSessionManager which was installed using cocoapods. When user is logging in they have their session cookie stored and they can perform different actions that require active session without providing their credentials more than once while the session is active. I have something around 50+ server calls that require user to be authenticated and which can return a JSON response stating that user's session is no longer valid and needs to be refreshed - by this event I should drop user out to login screen and ask them to re-login. I know that there's a method somewhere in the AFNetworking core that's processing raw responses for all the GET and POST methods before turning them into a responseObject of the id type but I cannot use it since I'm using cocoapods and can't really modify the core of a pod (I'm using git and pods aren't included in the commits). The question is - how do I handle user session expiration event centralized, not repeating it throughout 50+ different method calls?
I don't know if this is the best way or not, but I have solved this by creating a unified method to parsing error responses from server and put the check on whether user is authenticated or not into it like below and post an NSNotification I post when user logs out via logout button in app:
// in context of creating a request
success:^(NSURLSessionDataTask *task, id responseObject) {
NSString *errors = [self errors:responseObject[#"errors"]];
}
- (NSString *)errors:(id)errors
{
NSString *errMsg = nil;
if ([errors isKindOfClass:[NSDictionary class]]) {
errMsg = #"";
for (NSString *key in errors) {
errMsg = [errMsg stringByAppendingFormat:#"%#: ", key];
errMsg = [errMsg stringByAppendingString:[[errors objectForKey:key] componentsJoinedByString:#" "]];
errMsg = [errMsg stringByAppendingString:#"\n"];
}
}
else if ([errors isKindOfClass:[NSError class]]) {
if (!self.reachabilityManager.reachable) {
errMsg = #"Host isn't reachable. Please make sure you have an active Internet connection and try again later.";
}
else {
errMsg = [errors localizedDescription];
}
}
else if ([errors isKindOfClass:[NSString class]]) {
if ([errors isEqualToString:#"Operation requires authorized access."]) {
[CPAlertManager showMessage:#"Please re-authenticate." withTitle:#"Session expired."];
[[NSNotificationCenter defaultCenter] postNotificationName:CPUserLoginStatusChanged
object:#{ #"CPUserLoginStatus" : #(CPUserLoginStatusLoggedOut) }];
}
else {
errMsg = errors;
}
}
return errMsg;
}
Related
So far the remote wipe works, but I'm having trouble starting a backup in the background. The remote wipe also works in the background. I am trying to call the startBackgroundBackupActivity method from my locationHandler class which works in the background.
BackgroundBackupHandler.m
- (void) OnSyncComplete:(NSNumber*)result message:(NSString *)message{
NSLog(#"-(void)OnSyncComplete:%# message=%#",result, message);
//jxxtodo: Ensure all existing objects are reset, including DB and network connections
if (0 == [result intValue]){
NSString *strMsg = [ErrorHandler getErrorTextByErrorNumber:SYNC_SUCCESS_INF withObjects:nil];
[self logEvent:strMsg];
}else if (2 == [result intValue]){//no sync required
NSString *strMsg = [ErrorHandler getErrorTextByErrorNumber:NO_NEED_SYNC_INF withObjects:nil];
[self logEvent:strMsg];
}else if (3 == [result intValue]) {
NSString *strMsg = [ErrorHandler getErrorTextByErrorNumber:SYNC_RESET_EMPTY_INF withObjects:nil];
[self logEvent:strMsg];
} else{
NSString *strMsg = [ErrorHandler getErrorTextByErrorNumber:SYNC_COMMON_ERR withObjects:nil];
[self logEvent:strMsg];
return;
}
[m_pSyncController release];
m_pSyncController = nil;
self.m_backupSet = nil;
[self performSelector:#selector(startBackgroundBackupActivity) withObject:nil afterDelay:1.5];
}
Right now, OnSyncComplete:message: is what calls startBackupActcitivy from within the BackgroundBackupHandler class.
I have another class LocationHandler which checks the flag sent from the server and does something based on the flag. So if the flag is set to backup then wipe, it will run a backup then wipe the device.
How would I call OnSyncComplete:message: from the LocationHandler class.
Ive tried:
BackgroundBackupHandler *bgBackup = [[BackgroundBackupHandler alloc]init];
[bgBackup OnSyncComplete:[NSNumber numberWithInt:3] message:nil];
This is giving me errors and terminating my application. Is there anyway to call startBackgroundBackupActivity from the LocationHandlerClass.
The error is that the application crashed and aborts. NSInvalidArgumentException, where nil is not a legal NSManagedObjectContext.
The locationHandler will start a background task, which will then sync the device and return control to the OnSyncComplete method in the LocationHandler class which then in turn calls the OnSyncComplete in the BackgroundBackupHandler class.
The 2 ways to create communication between object are:
1) delegations
2) notifications
In your case it seems like the notification can work better.
Check this out
Im digging into Apple's Touch ID, more precisely the Local Authenticator.
The documentation as of now is pretty sparse.
Its mainly just this:
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = <#String explaining why app needs authentication#>;
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL success, NSError *error) {
if (success) {
// User authenticated successfully, take appropriate action
} else {
// User did not authenticate successfully, look at error and take appropriate action
}
}];
} else {
// Could not evaluate policy; look at authError and present an appropriate message to user
}
as taken from https://developer.apple.com/documentation/localauthentication
The idea of using your fingerprint for authentication is nice. But I can add fingerprints in the device if I know the passcode. And its very easy to get the passcode, like you sit in the train next to ur victim and watch him/her enter the passcode.
I want to use the fingerprint as a way of secure authentication but want to be able to detect if new fingerprints were added since the last time I requested the fingerprint.
Apple is doing this for the AppStore. If you want to authenticate a transaction in the AppStore and have added a new Fingerprint since your last transaction, the AppStore requests your AppleId-Password. This is sane behaviour, because the phone might have been taken by someone else who knows the passcode and added his own fingerprint to buy something expensive.
My Question: Can I detect if a new fingerprint was added since the last time that I used Local Authenticator?
This is now possible in iOS9. The property evaluatedPolicyDomainState has been added to LAContext.
If the fingerprint database is modified (fingers were added or removed), the data returned by evaluatedPolicyDomainState will change. The nature of the changes cannot be determined but by comparing data of evaluatedPolicyDomainState after different evaluatePolicy calls you can detect that the set of fingerprints has been modified.
Note that this property is set only when evaluatePolicy is called and a succesful Touch ID authentication was performed, or when canEvaluatePolicy succeeds for a biometric policy.
As Keith stated, in iOS 9 it's possible. You should do it like this.
let context = LAContext()
context.canEvaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, error: nil)
if let domainState = context.evaluatedPolicyDomainState
where domainState == oldDomainState {
// Enrollment state the same
} else {
// Enrollment state changed
}
Every time you add or delete a fingerprint, the domain state changes. You need to call canEvaluatePolicy for evaluatedPolicyDomainStateto be updated.
In short; no.
In a bit more detail; the LocalAuthentication framework is a tightly-guarded black box. The information you get back from it is very limited. Your interaction with it goes something like this:
Ask it if it's able to authenticate for some type of policy (there is only 1 available at time of writing - Biometrics (Touch ID))
If it can, ask it to actually do it
The system takes over for the actual authentication
It lets you know if the authentication was successful or not (if not, it tells you why)
You have no concept of the actual authentication process (which finger was used, for example). This, of course, is by design. Apple does not want, nor need, to give you access to such information.
I would recommend to store the evaluatedPolicyDomainState value into keychain instead of storing it in NSUserDefault.
You can convert the data value of evaluatedPolicyDomainState into string, which is a 44 character string. Below is the code to convert the evaluatedPolicyDomainState data value into string -
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) {
if let domainState = context.evaluatedPolicyDomainState {
let bData = domainState.base64EncodedData()
if let decodedString = String(data: bData, encoding: .utf8) {
print("Decoded Value: \(decodedString)")
}
}
}
Now if the device owner made any change in Touch ID like adding a new finger Id; then this data value will be changed and you can take necessary steps to handle the change based on your project needs.
This is the solution to verify if a fingerprint was added or removed, and the difference between the Swift and ObjC solution is that canEvaluatePolicy just verify if something changes, while evaluatePolicy opens the modal verification.
Swift 5.2
let context = LAContext()
context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
let defaults = UserDefaults.standard
let oldDomainState = defaults.object(forKey: "domainTouchID") as? Data
if let domainState = context.evaluatedPolicyDomainState, domainState == oldDomainState {
// Enrollment state the same
print("nothing change")
} else {
// Enrollment state changed
print("domain state was changed")
}
// save the domain state for the next time
defaults.set(context.evaluatedPolicyDomainState, forKey: "domainTouchID")
Objective-C
- (void)evaluatedPolicyDomainState {
LAContext *context = [[LAContext alloc] init];
__block NSString *message;
// show the authentication UI with reason string
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:#"Unlock access to locked feature" reply:^(BOOL success, NSError *authenticationError) {
if (success) {
// load the last domain state from touch id
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *oldDomainState = [defaults objectForKey:#"domainTouchID"];
NSData *domainState = [context evaluatedPolicyDomainState];
// check for domain state changes
if ([oldDomainState isEqual:domainState]) {
message = #"nothing change";
} else {
message = #"domain state was changed";
}
// save the domain state that will be loaded next time
oldDomainState = [context evaluatedPolicyDomainState];
[defaults setObject:oldDomainState forKey:#"domainTouchID"];
[defaults synchronize];
} else {
message = [NSString stringWithFormat:#"evaluatePolicy: %#", authenticationError.localizedDescription];
}
[self printMessage:message inTextView:self.textView];
}];
}
I would like to add something,
-(BOOL)hasFingerPrintChanged
{
BOOL changed = NO;
LAContext *context = [[LAContext alloc] init];
[context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:nil];
NSData *domainState = [context evaluatedPolicyDomainState];
// load the last domain state from touch id
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *oldDomainState = [defaults objectForKey:#"domainTouchID"];
if (oldDomainState)
{
// check for domain state changes
if ([oldDomainState isEqual:domainState])
{
NSLog(#"nothing changed.");
}
else
{
changed = YES;
NSLog(#"domain state was changed!");
NSString *message = #"Your Touch ID is invalidated, because you have added or removed finger(s).";
}
}
// save the domain state that will be loaded next time
[defaults setObject:domainState forKey:#"domainTouchID"];
[defaults synchronize];
return changed;
}
It's better to store user password etc in keychain.
I'm using https://github.com/reidmain/FDKeychain
today I've tried to send my updated app and the apple declined my app for using uniqueIdentifier. After some research I found, that libPayPalEC.a is using uniqueIdentifier 3 times.
Via terminal: strings .Lib/PayPal/libPayPalEC.a | grep uniqueIdentifier
Does any one now where I can download updated library? I was looking everywhere...
Thank you,
Please refer to the issue here on github: https://github.com/paypal/PayPal-iOS-SDK/issues/13
After some reasearch and great answer on github: https://github.com/paypal/PayPal-iOS-SDK/issues/13#issuecomment-17882240 I've removed paypal library, removed deviceToken generation and the PayPal mobile is working!
How to use PayPal payment without library:
You need to create the payment process on the backend (like PHP) and in the iOS app just open the web view with URL to your backend. To process success or error handle the web view request so you can catch link like error, cancel or success (the pages, which the backend redirects).
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSString *urlString = [[request.URL absoluteString] lowercaseString];
if (urlString.length > 0) {
//80 is the default HTTP port.
//The PayPal server may add the default port to the URL.
//This will break our string comparisons.
if ([request.URL.port intValue] == 80) {
urlString = [urlString stringByReplacingOccurrencesOfString:#":80" withString:#""];
}
NSLog(#"URL %#",urlString);
if ([urlString rangeOfString:#"success"].location != NSNotFound) {
// handle error
if (visible) {
[self dismissViewControllerAnimated:YES completion:complete];
} else {
dismissOnAppear = YES;
}
return FALSE;
} else if ([urlString rangeOfString:#"cancel"].location != NSNotFound) {
// handle error
if (visible) {
[self dismissViewControllerAnimated:YES completion:complete];
} else {
dismissOnAppear = YES;
}
return FALSE;
} else if ([urlString rangeOfString:#"error"].location != NSNotFound) {
// handle error
if (visible) {
[self dismissViewControllerAnimated:YES completion:complete];
} else {
dismissOnAppear = YES;
}
return FALSE;
}
}
return TRUE;
}
On the backend handling you don't need to sent dirt token (from the iOS library) any more, you just work with token from paypal as the standard way.
As the payment method use _express-checkout not _express-checkout-mobile and don't send drt as deviceToken.
I am following this link: https://github.com/yahoo/yos-social-objc for retrieving yahoo contacts.
After providing all the credentials (i.e secret key, consumer key, app id) it is going to browser for login. But after logged in, it's displaying this message:
To complete sharing of yahoo! info with xxxx, enter code xxxx into xxxx
So, I am not getting that where I should enter this code? And how will it redirect to my application.
Any help will be appreciated.
CloudSponge has an iOS widget for its contact importer. Visit our test drive page from your iOS device to see how it works.
I work for CloudSponge, please let me know if you have any questions.
You need to specify your callback url. By default it's "oob" and will give you the verifier code. It will be better if you present your own web view and monitor the verifier code through webview delegates. Here's how you do it.
YOSSession *yahooSession; //instance variable
- (IBAction)yahooButtonAction:(UIButton *)sender {
yahooSession = [YOSSession sessionWithConsumerKey:YAHOO_CONSUMER_KEY
andConsumerSecret:YAHOO_CONSUMER_SECRET
andApplicationId:YAHOO_APP_ID];
// try to resume a user session if one exists
BOOL hasSession = [yahooSession resumeSession];
if(hasSession == FALSE) {
[self fetchSession];
}else{
[self sendRequests];
}
}
-(void)fetchSession{
// create a new YOSAuthRequest used to fetch OAuth tokens.
YOSAuthRequest *tokenAuthRequest = [YOSAuthRequest requestWithSession:yahooSession];
// fetch a new request token from oauth.
YOSRequestToken *newRequestToken = [tokenAuthRequest fetchRequestTokenWithCallbackUrl:#"http://localhost"];
// if it looks like we have a valid request token
if(newRequestToken && newRequestToken.key && newRequestToken.secret) {
// store the request token for later use
[yahooSession setRequestToken:newRequestToken];
[yahooSession saveSession];
// create an authorization URL for the request token
NSURL *authorizationUrl = [tokenAuthRequest authUrlForRequestToken:yahooSession.requestToken];
[self presentWebViewForYahooWithAuthURL:authorizationUrl];
//present it in webview
} else {
// NSLog(#"error fetching request token. check your consumer key and secret.");
}
}
-(void) presentWebViewForYahooWithAuthURL:(NSURL *)url{
_yahooWebView = [[UIWebView alloc] initWithFrame:self.view.frame];
_yahooWebView.delegate=self; //so that we can observe the url for verifier
[_yahooWebView loadRequest:[NSURLRequest requestWithURL:url]];
[self.view addSubview:_yahooWebView];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
NSString *requestString = request.URL.absoluteString;
if ([requestString rangeOfString:#"http://localhost"].length>0) {
NSRange verifierRange = [requestString rangeOfString:#"oauth_verifier="];
if (verifierRange.length>0) {
verifierRange.location =verifierRange.location+verifierRange.length;
verifierRange.length = requestString.length-verifierRange.location;
NSLog(#"Verifier => %#", [requestString substringWithRange:verifierRange]);
yahooSession.verifier=[requestString substringWithRange:verifierRange];
[self sendRequests];
}
return NO;
}
else{
return YES;
}
}
I am working with authenticating user to use the google account he is associated with. The problem is that everytime the user logs in through my app, the "Allow Access" always appears on the Google's authentication view even I had clicked the Allow Access already from previous test. Is this normal or am I doing my codes wrong? Please help me guys.
I used the following codes for loggin in an out:
- (IBAction)signIn:(id)sender {
if(!isSignedIn){
[self signOutFromAll];
NSString *keychainItemName = nil;
// save keychain
keychainItemName = kKeychainItemName;
NSString *scope = #"https://www.googleapis.com/auth/plus.me";
NSString *clientID = kClientID;
NSString *clientSecret = kClientSecret;
SEL finishedSel = #selector(viewController:finishedWithAuth:error:);
GTMOAuth2ViewControllerTouch *viewController;
viewController = [GTMOAuth2ViewControllerTouch controllerWithScope:scope
clientID:clientID
clientSecret:clientSecret
keychainItemName:keychainItemName
delegate:self
finishedSelector:finishedSel];
[[self navigationController]pushViewController:viewController animated:YES];
} else {
[self displayAlertWithMessage:#"Currently Signed in."];
} }
- (IBAction)signOut:(id)sender {
[self signOutFromAll];
[self displayAlertWithMessage:#"Signed out."]; }
This is for the delegate:
- (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
finishedWithAuth:(GTMOAuth2Authentication *)auth
error:(NSError *)error{
if(error != nil){
// Authentication failed...
NSLog(#"Authentication error: %#", error);
NSData *responseData = [[error userInfo] objectForKey:#"data"];
if([responseData length] > 0)
NSLog(#"%#", [[[NSString alloc]initWithData:responseData encoding:NSUTF8StringEncoding]autorelease]);
self.auth = nil;
} else {
// Authentication succeeded...
isSignedIn = YES;
self.auth = auth;
}
}
And awakeFromNib:
- (void)awakeFromNib{
// Fill in the Client ID and Client Secret text fields
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// First, we'll try to get the saved Google authentication, if any, from the keychain
// Normal applications will hardcode in their client ID and client secret,
// But the sample app allows the user to enter them in a text field, and saves them in the preferences
NSString *clientID = [defaults stringForKey:kGoogleClientIDKey];
NSString *clientSecret = [defaults stringForKey:kGoogleClientSecretKey];
GTMOAuth2Authentication *auth;
auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName
clientID:clientID
clientSecret:clientSecret];
if (auth.canAuthorize) {
// There is saved google authentication
// self.serviceSegments.selectedSegmentIndex = 0;
}
// Save the authentication object, which holds the auth tokens
self.auth = auth;
[self setAuth:auth];
isSignedIn = self.auth.canAuthorize;
}
By the way my reference for these codes is on this link: http://code.google.com/p/gtm-oauth2/wiki/Introduction#Using_the_OAuth_2_Controllers
from the docs:
The keychain item name is used to save the token on the user’s keychain, and should identify both your application name and the service name(s). If keychainItemName is nil, the token will not be saved, and the user will have to sign in again the next time the application is run.
http://code.google.com/p/gtm-oauth2/wiki/Introduction
So, from your code, it depends on what kKeychainItemName is set to.
Just thought I'd comment on this as I was reading the docs.
Use this method when you get the oauth object to save into keychain
[GTMOAuth2ViewControllerTouch saveParamsToKeychainForName:YOUR_KEYCHAIN_ITEM_NAME authentication:auth];
and
before making a call to api just check and retrieve the oauth object using this
GTMOAuth2Authentication * auth = [GTMOAuth2ViewControllerTouch
authForGoogleFromKeychainForName:YOUR_KEYCHAIN_ITEM_NAME
clientID:GOOGLE_CLIENT_KEY
clientSecret:GOOGLE_CLIENT_SECRET];
and make sure it's oauth object is authentic with using this
if(![GTMOAuth2ViewControllerTouch authorizeFromKeychainForName:YOUR_KEYCHAIN_ITEM_NAME authentication:auth])
I know this is an old question, but I encountered the same issue so I'm writing my solution, it might help somebody else in the future.
Turns out it's not sufficient to only set self.auth, you also need to set the self.analyticsService.authorizer variable
if ([self.auth canAuthorize])
{
self.analyticsService.authorizer = self.auth;
[self getAnalyticsData];
return;
}
This did the trick for me, the user is no longer asked to enter the credentials.
Put the below code to logout / sign out from Google SDK.
- Call below function from where you want:
static NSString *const kKeychainItemName = #"MY_APP";
- (void)logoutFromGoogleDrive {
[GTMOAuth2SignIn revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)self.driveService.authorizer];
[GTMOAuth2ViewControllerTouch saveParamsToKeychainForName:kKeychainItemName authentication:nil];
}
[Note: Above code works, if you have used GTMOAuth2SignIn for sign in user for google access like,
GTMOAuth2Authentication * auth = [GTMOAuth2ViewControllerTouch
authForGoogleFromKeychainForName:YOUR_KEYCHAIN_ITEM_NAME
clientID:GOOGLE_CLIENT_KEY
clientSecret:GOOGLE_CLIENT_SECRET];
]
From my experience, this behavior is normal.
Are you having doubts because facebook only asks the user once if the user wants to grant the app privileges to access the user's profile?