I want to use TouchID authenticate my own app.
1.I want user can click 'Enter passcode' to invoke system build-in passcode screen to authenticate,if success then enter my own app.
But I don't how know to make it forward to passcode authenticate view like the following screen in 'case LAErrorUserFallback'
Here is my code :
LAContext *context = [[LAContext alloc] init];
__block NSString *msg;
__block BOOL bAuth;
// show the authentication UI with our reason string
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:NSLocalizedString(#"Unlock APP With FingerPrint", nil) reply:
^(BOOL success, NSError *authenticationError) {
if (success) {
bAuth = YES;
msg =[NSString stringWithFormat:NSLocalizedString(#"EVALUATE_POLICY_SUCCESS", nil)];
dispatch_async(dispatch_get_main_queue(), ^{
[[MYAppDelegate theDelegate] initializeAppAfterKeyVaultUnlocked];
});
NSLog(#"%#",msg);
} else {
bAuth = NO;
switch (authenticationError.code) {
case LAErrorAuthenticationFailed:
msg = [NSString stringWithFormat:NSLocalizedString(#"Authentication Failed", nil)];
// ...
break;
case LAErrorUserCancel:
msg = [NSString stringWithFormat:NSLocalizedString(#"User pressed Cancel button", nil)];
dispatch_async(dispatch_get_main_queue(), ^{
[[MYAppDelegate theDelegate] exitAndLock];
});
break;
case LAErrorUserFallback:
msg = [NSString stringWithFormat:NSLocalizedString(#"User pressed \"Enter Password\"", nil)];
//Here I want to fallback to iOS build-in passcode authenticate view, and get the auth result.
break;
default:
msg = [NSString stringWithFormat:NSLocalizedString(#"Touch ID is not configured", nil)];
// ...
break;
}
NSLog(#"%#",authenticationError.localizedDescription);
}
}];
Now in iOS 9 it is pretty simple actually - you just have to use LAPolicyDeviceOwnerAuthentication instead of LAPolicyDeviceOwnerAuthenticationWithBiometrics
So in your code you just replace this:
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:NSLocalizedString(#"Unlock APP With FingerPrint", nil) reply:
^(BOOL success, NSError *authenticationError) {
With this:
[context evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason:NSLocalizedString(#"Unlock APP With FingerPrint", nil) reply:
^(BOOL success, NSError *authenticationError) {
Thus, when user fails to authenticate with fingerprint, there'd be "enter passcode" option which would invoke system passcode input screen.
In my understanding, you will have to build the passcode screen yourself if you want to use evaluatePolicy.
If you use keychain, the SecItemCopyMatching function automatically falls back to passcode if fingering fails. Here's a reference on how to get it going (see section 3): https://www.secsign.com/fingerprint-validation-as-an-alternative-to-passcodes/
Didn't try that, but this post claim you can use the system as follow here (This works only with iOS 8 or later).
Or (which is what I did) you can build your passcode entry screen (to support older iOS versions), my controller have a passcode entry view, which will get exposed when the user select use passcode. At that point the call back from evaluatePolicy will return with LAErrorUserFallback, which can be the time to open your custom passcode screen.
something like that:
-(void)maybeShowTouchIDMessage {
if (![SettingsManager sharedManager].isUseTouchID || self.createPassCodeMode) {
self.shieldView.hidden = YES;
return;
}
self.shieldView.hidden = NO;
self.shieldView.alpha = 1.0;
LAContext *context = [[LAContext alloc] init];
NSError *evalError = nil;
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&evalError] ) {
__weak SecurityWindowViewController *weakSelf = self;
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:#"Use touch id \n or hit \"Cancel\" to enter passcode"
reply:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
SecurityWindowViewController *strongSelf = weakSelf;
if (success) {
[strongSelf hideWithSuccess:YES];
} else if (error){
NSString *errorMessage;
BOOL showError = NO;
switch (error.code) {
case LAErrorAuthenticationFailed:
errorMessage = #"Sorry couldn't autheticate";
showError = YES;
break;
case LAErrorPasscodeNotSet:
errorMessage = #"Sorry couldn't autheticate";
showError = YES;
break;
case LAErrorTouchIDNotEnrolled:
errorMessage = #"Touch ID has no enrolled fingers";
showError = YES;
break;
default:
showError = NO;
break;
}
[UIView animateWithDuration:0.5 animations:^{
strongSelf.shieldView.alpha = 0.0;
} completion:^(BOOL finished) {
strongSelf.shieldView.hidden = YES;
}];
if (showError) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:errorMessage
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
}
}
});
}];
}
For your case :
case LAErrorUserFallback:
[context evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason:NSLocalizedString(#"Unlock APP With PassCode", nil) reply: ^(BOOL success, NSError *authenticationError) {
if(success){
NSLog(#"PassCode Login successful");
}else{
NSLog(#"%#",authenticationError);
}
}
For device passcode verification you need to use LAPolicyDeviceOwnerAuthentication instead of LAPolicyDeviceOwnerAuthenticationWithBiometrics. Hope this helps !!
You can add another case and call your passcode screen from that.
Here is my code:
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = strMessage;
objFlockr.pVerificationBlock = objResponse;
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
if (!isShow) {
myContext.localizedFallbackTitle = #"";
}
else
{
myContext.localizedFallbackTitle = #"Set Up Passcode";
}
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL succes, NSError *error) {
if (!AppDel.firstAttampt && succes && !isShow)
{
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(1);
}
else if (succes) {
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(0);
NSLog(#"User authenticated");
} else {
switch (error.code) {
case LAErrorAuthenticationFailed:
NSLog(#"Authentication Failed");
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(1);
break;
case LAErrorUserCancel:
NSLog(#"User pressed Cancel button");
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(3);
break;
case LAErrorUserFallback:
NSLog(#"User pressed \"Enter Password\"");
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(4);
break;
default:
[self showMessage:#"Touch ID is not configured" withTitle:#"Error"];
if (objFlockr.pVerificationBlock)
objFlockr.pVerificationBlock(2);
NSLog(#"Touch ID is not configured");
break;
}
NSLog(#"Authentication Fails");
}
}];
} else {
NSLog(#"Can not evaluate Touch ID");
[self showMessage:#"Can not evaluate TouchID" withTitle:#"Error"];
}
Replace LAPolicy policy enum value deviceOwnerAuthenticationWithBiometrics with deviceOwnerAuthentication
Note: If user has enable biometric (face id or touch id) authentication, then device will ask first for biometric authentication and if user choose fall back authentication, then only deviceOwnerAuthentication will show passcode screen.
Try this and see (swift 4):
func touchIDAuthentication() {
let context = LAContext()
var error:NSError?
// edit line - deviceOwnerAuthentication
guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
//showAlertViewIfNoBiometricSensorHasBeenDetected()
return
}
// edit line - deviceOwnerAuthentication
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &errorPointer) {
// edit line - deviceOwnerAuthentication
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason, reply: { (success, error) in
if success {
DispatchQueue.main.async {
print("Authentication was successful")
}
}else {
DispatchQueue.main.async {
//self.displayErrorMessage(error: error as! LAError )
print("Authentication was error")
}
}
})
}else {
// self.showAlertWith(title: "Error", message: (errorPointer?.localizedDescription)!)
}
}
Related
Situation
I am trying to change an Objective-C project from Parse to firebase. What I am trying to do is replacing Parse codes with corresponding firebase code. This strategy worked for my login page and login was successful
The login page calls a segue to MainViewController. The MainViewController loads but the app crashes with an error
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot do a comparison query for type: (null)'
What I Did
This is the firebase code I added in LoginViewController - (IBAction)loginButtonClicked:(id)sender
//Checking for username and password
_emailId=_emailTextField.text;
_password=_passwordTextField.text;
//reference for firebase
self.ref = [[FIRDatabase database] reference];
if([self isInternet])
{
//firebase authentication
[[FIRAuth auth]signInWithEmail:_emailId password:_password completion:^(FIRUser *user, NSError *error) {
// ...
if (error) {
//login failed
[self failsLoginWithError:#"Please try again later"];
}
else
{
NSLog(#"Login Successful");
[self successLoginWithResposne:user];
}
and this is the parse code I commented out
PFQuery *query=[PFQuery queryWithClassName:#"normal"];
[query whereKey:#"usermail" equalTo:_emailId];
[query whereKey:#"password" equalTo:_password];
[query whereKey:#"user_removed" equalTo:#(0)];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
//login failed
[self failsLoginWithError:#"Please try again later"];
}
if ([objects count]==0) {
NSLog(#"Invalid username or passowrd");
[self failsLoginWithError:#"Invalid email or password"];
} else {
PFObject *obj = [objects firstObject];
ud = [NSUserDefaults standardUserDefaults];
BOOL isBlocked = [[obj valueForKey:#"blocked"] boolValue];
BOOL isLogged = [[obj valueForKey:#"logged"]boolValue];
NSString *userID = [ud objectForKey:#"ObjectID"];
if (isLogged) {
if ([userID isEqualToString:obj.objectId]) {
if (isBlocked) {
[self failsLoginWithError:#"Login Blocked! Please contact your Doctor"];
}else{
// Login success
[self successLoginWithResposne:obj];
}
}
else{
// login Failed
[obj setValue:#(1) forKey:#"blocked"];
[obj saveInBackground];
[self failsLoginWithError:#"Login Blocked! Please contact your Doctor"];
}
}
else {
// login success
[obj setValue:#(1) forKey:#"logged"];
[obj saveInBackground];
[self successLoginWithResposne:obj];
}
}
} ];
The error message says "exception due to comparison with a null value".
Guess this is due to some parse code somewhere comparing a value that is received from the LoginViewController (which I commented out).
Question
How to fix this?
How do I find the part where the comparison is happening?
Do I have to replace every parse code with firebase code before executing?
if so, How to find every parse code in the project?
Crash report:
Exact line where code crashes:
You may look for the User system parse has build in
Example
[PFUser logInWithUsernameInBackground:_emailId password:_password
block:^(PFUser *user, NSError *error) {
if (user) {
if(user[#"blocked"] == #YES){
return [PFUser logOut];
}else{
// USER AUTHENTICATED
}
} else {
// Password, Conection
}
}];
All local store will be available by using this anywhare in your app:
PFUser *currentUser = [PFUser currentUser];
if (currentUser) {
// do stuff with the user
} else {
// show the signup or login screen
}
If you want to auth with the email insted of a "username" just use the username as the email in registration.
This working perfectly in iOS 8.
But creating issue in iOS 9.Here is code :
self.eventManager.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted) {
// Create a new calendar.
EKCalendar *calendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent
eventStore:self.eventManager.eventStore];
// Set the calendar title.
calendar.title = #"<APP name>";
calendar.CGColor=APP_Blue_COLOR.CGColor;
// Find the proper source type value.
for (int i=0; i<self.eventManager.eventStore.sources.count; i++) {
EKSource *source = (EKSource *)[self.eventManager.eventStore.sources objectAtIndex:i];
EKSourceType currentSourceType = source.sourceType;
if (currentSourceType == EKSourceTypeLocal) {
calendar.source = source;
break;
}
}
// Save and commit the calendar.
NSError *error;
[self.eventManager.eventStore saveCalendar:calendar commit:YES error:&error];
// If no error occurs then turn the editing mode off, store the new calendar identifier and reload the calendars.
if (error == nil) {
// Turn off the edit mode.
// Store the calendar identifier.
[self.eventManager saveCustomCalendarIdentifier:calendar.calendarIdentifier];self.eventManager.selectedCalendarIdentifier=calendar.calendarIdentifier;//chirag
}
else{
// Display the error description to the debugger.
NSLog(#"CREATE_CALENDER %#", [error localizedDescription]);
}
}
else
{
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:#"" message:#"Please give permission to access your iPhone calender." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
}
}];
It give me success message but not creating my app calendar in iPhone calendar.
I though that it does not showing it due to no event set to it.so I also tried to set new event.
But it give me following code & error while creating new event.
// Create a new event object.
EKEvent *event = [EKEvent eventWithEventStore:self.eventManager.eventStore];
// Set the event title.
event.title = title;
// Set its calendar.
event.calendar = [self.eventManager.eventStore calendarWithIdentifier:self.eventManager.selectedCalendarIdentifier];
// Set the start and end dates to the event.
event.startDate = startDate;
event.endDate = endDate;
// Save and commit the event.
NSError *error;
if ([self.eventManager.eventStore saveEvent:event span:EKSpanThisEvent commit:YES error:&error]) {
// Call the delegate method to notify the caller class (the ViewController class) that the event was saved.
return true;
}
else{
// An error occurred, so log the error description.
NSLog(#"%#", [error localizedDescription]);
return false;
}
It give following error internally however it return will in NSError object:
Error getting shared calendar invitations for entity types 3 from daemon: Error Domain=EKCADErrorDomain Code=1014 "(null)"
The problem is that when iCloud calendars switched on, it hides the locally created ones from the calendar app. To bypass this problem the solution is to add a new calendar to iCloud source:
for (EKSource *source in self.eventStore.sources)
{
if (source.sourceType == EKSourceTypeCalDAV &&
[source.title isEqualToString:#"iCloud"]) //This is a patch.
{
localSource = source;
break;
}
}
if (localSource == nil)
{
for (EKSource *source in self.eventStore.sources)
{
if (source.sourceType == EKSourceTypeLocal)
{
localSource = source;
break;
}
}
}
I am new to game centre and I am building multiplayer game. I have different users in my tableview; now I want to send an invite for a match to a specific user. For sending the invitation I am using this code:
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = 2;
request.maxPlayers = 2;
request.playersToInvite = [[NSArray alloc] initWithObjects:player.playerId, nil];
request.inviteMessage = #"Your Custom Invitation Message Here";
request.inviteeResponseHandler = ^(NSString *playerID, GKInviteeResponse response)
{
[self updateUIForPlayer: playerID accepted: (response == GKInviteeResponseAccepted)];
};
But how to receive that invitation? I think I have to implement a method for this in GameKitHelper class but I am not sure how to handle this.
my teacher helped me with this and i thought to post working code here if anyone stuck here can use this code.
- (void)inviteFriends: (NSArray*) friends
{
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = 2;
request.maxPlayers = 2;
request.defaultNumberOfPlayers = 2;
request.recipients = friends;
request.inviteMessage = #"Your Custom Invitation Message Here";
[[GKMatchmaker sharedMatchmaker] findMatchForRequest:request withCompletionHandler:^(GKMatch* match, NSError *error) {
if (error)
{
//Invite has not been sent
// //NSLog(#"Invitation has not been sent");
}
else if (match != nil)
{
//whatever you want to do when the receiver accepts the invite
//NSLog(#"Invitation has been sent with match object = %#",match);
}
}];
request.recipientResponseHandler= ^(GKPlayer *player, GKInviteeResponse response)
{
//NSLog(#"response Get From Other User.");
switch (response) {
case GKInviteeResponseAccepted:
{
}
break;
case GKInviteeResponseDeclined:
{
}
break;
case GKInviteeResponseFailed:
{
}
break;
case GKInviteeResponseIncompatible:
{
}
break;
case GKInviteeResponseUnableToConnect:
{
}
break;
case GKInviteeResponseNoAnswer:
{
}
break;
default:
break;
}
};
}
i using this code in iOS 8 for security and uses touch ID
- (IBAction)authenticateButtonTapped{
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = #"Authenticate using your finger\r Scan Your Finger Now";
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL succes, NSError *error) {
if (succes) {
[self showMessage:#"Authentication is successful" withTitle:#"Success"];
NSLog(#"User authenticated");
} else {
switch (error.code) {
case LAErrorAuthenticationFailed:
[self showMessage:#"Authentication is failed" withTitle:#"Error"];
NSLog(#"Authentication Failed");
break;
case LAErrorUserCancel:
[self showMessage:#"You clicked on Cancel" withTitle:#"Error"];
NSLog(#"User pressed Cancel button");
break;
case LAErrorUserFallback:
[self showMessage:#"You clicked on \"Enter Password\"" withTitle:#"Error"];
NSLog(#"User pressed \"Enter Password\"");
[self copyMatchingAsync];
break;
default:
[self showMessage:#"Touch ID is not configured" withTitle:#"Error"];
NSLog(#"Touch ID is not configured");
break;
}
NSLog(#"Authentication Fails");
}
}];
} else {
NSLog(#"Can not evaluate Touch ID");
[self showMessage:#"Can not evaluate TouchID" withTitle:#"Error"];
}
}
after for use the passcode system i copy this code from apple example
- (void)copyMatchingAsync
{
NSDictionary *query = #{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: #"SampleService",
(__bridge id)kSecReturnData: #YES,
(__bridge id)kSecUseOperationPrompt: NSLocalizedString(#"AUTHENTICATE_TO_ACCESS_SERVICE_PASSWORD", nil)
};
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CFTypeRef dataTypeRef = NULL;
NSString *msg;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
if (status == errSecSuccess)
{
NSData *resultData = ( __bridge_transfer NSData *)dataTypeRef;
NSString * result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
msg = [NSString stringWithFormat:NSLocalizedString(#"RESULT", nil), result];
} else {
}
});
}
-(void) showMessage:(NSString*)message withTitle:(NSString *)title
{
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* cancel = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[alert dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:cancel];
[self presentViewController:alert animated:YES completion:nil];
}
its work great and rapid for fingerprint but the passcode system can't show and dosen't work. and i received "ERROR_ITEM_NOT_FOUND" = "error item not found";
this apple link https://developer.apple.com/library/ios/samplecode/KeychainTouchID/Introduction/Intro.html
but i can't good understand
Sorry to tell you but you can't do that.
You are mixing access control with keychain access, You don't have access to the user passcode.
using SecItemCopyMatching is possible if you added a resource with SecItemAdd using the same attributes (the contents of "query")
"ERROR_ITEM_NOT_FOUND" = "error item not found" show no item in keychain.
I also saw the Apple sample code "KeychainTouchID" as you said above.
iOS8's new feature make the developer can use the iPhone user's passcode & Touch ID for authentication.
You have to "Add Item" firstly, and then call "Query for Item".
If you want more convenient solution, maybe you can use SmileTouchID.
It is a library for integrate Touch ID & Passcode to iOS App conveniently that even support for iOS7 and the device that cannot support Touch ID.
You just need a few line of code and get what you want.
if ([SmileAuthenticator hasPassword]) {
[SmileAuthenticator sharedInstance].securityType = INPUT_TOUCHID;
[[SmileAuthenticator sharedInstance] presentAuthViewController];
}
- (ACAccount *)accountFacebook{
if (_accountFacebook) {
return _accountFacebook;
}
if (!_accountStoreFacebook) {
_accountStoreFacebook = ACAccountStore.new;
}
ACAccountType *accountTypeFacebook = [self.accountStoreFacebook accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
NSDictionary *options = #{ACFacebookAppIdKey : #"xxxxxxxxx",
ACFacebookAudienceKey : ACFacebookAudienceEveryone,
ACFacebookPermissionsKey : #[#"user_about_me", #"publish_actions"]
};
__block ACAccount *accountFb;
[_accountStoreFacebook requestAccessToAccountsWithType:accountTypeFacebook options:options completion:^(BOOL granted, NSError *error) {
if (granted) {
NSLog(#"Facebook access granted");
accountFb = _accountStoreFacebook.accounts.lastObject;
}else {
NSLog(#"Facebook access denied");
accountFb = nil;}
if (error) {
NSLog(error.localizedDescription);
}
}];
return accountFb;
}
When I run
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
if (appDelegate.accountFacebook) {
NSLog(#"accountFacebook OK");
}else NSLog(#"accountFacebook Not Exists");
appDelegate.accountFacebook return nil always, doesn't wait for block to complete.
What should be changed?
This is an asynchronous call, so the block completes after your method ends. You need to redesign your app to do what it has to do in the completion block. You call appDelegate.accountFacebook and expect to do something if it is not nil. Why not pass this method a completion block that would perform what you want it to perform like so:
typedef void(^HandlerType)(ACAccount* account);
- (void)performForFacebookAccount: (HandlerType) handler{
if (_accountFacebook) {
handler(_accountFacebook);
return;
}
if (!_accountStoreFacebook) {
_accountStoreFacebook = ACAccountStore.new;
}
ACAccountType *accountTypeFacebook = [self.accountStoreFacebook accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
NSDictionary *options = #{ACFacebookAppIdKey : #"xxxxxxxxx",
ACFacebookAudienceKey : ACFacebookAudienceEveryone,
ACFacebookPermissionsKey : #[#"user_about_me", #"publish_actions"]
};
[_accountStoreFacebook requestAccessToAccountsWithType:accountTypeFacebook options:options completion:^(BOOL granted, NSError *error) {
if (granted) {
NSLog(#"Facebook access granted");
_accountFacebook = _accountStoreFacebook.accounts.lastObject;
handler(_accountFacebook);
}else {
NSLog(#"Facebook access denied");
_accountFacebook = nil;}
if (error) {
NSLog(error.localizedDescription);
}
}];
}