How to sign in with apple logout - ios

When using Apple login, I want to know how to log out. Two revoking methods I know
[1] Upon opening your app
Open the Settings app, then tap [your name].
Tap Password & Security
Tap Apps Using Your Apple ID.
let authorizationAppleIDProvider = ASAuthorizationAppleIDProvider()
authorizationAppleIDProvider.getCredentialState(forUserID: "currentUserIdentifier") { (credentialState: ASAuthorizationAppleIDProvider.CredentialState, error: Error?) in
if let error = error {
print(error)
// Something went wrong check error state
return
}
switch (credentialState) {
case .authorized:
//User is authorized to continue using your app
break
case .revoked:
//User has revoked access to your app
break
case .notFound:
//User is not found, meaning that the user never signed in through Apple ID
break
default: break
}
}
How to know through this code when running the app afterwards
[2] While your app is running
let notificationCenter = NotificationCenter.default
let sessionNotificationName = NSNotification.Name.ASAuthorizationAppleIDProviderCredentialRevoked
appleIDSessionObserver = notificationCenter.addObserver(forName: sessionNotificationName, object: nil, queue: nil) { (notification: Notification) in
//Sign user out
}
This code notifies when a credentialState becomes revoked when a user session changes.
What I'm curious about is [2] While your app is running, in what situation is the user session revoked in the app? For example, if you press the logout button, credentialState becomes revoked, you will receive notifications and execute the logout function, right? How to revoked appleId CredentialState in the context of using the app?

Related

How to figure out "Sign in with Apple" in user with multiple devices with same apple id?

Here is my scenario.
Someone has iPhone and iPad (iOS 13.0 or later), and he signed up with same appleID.
And he `sign in with apple` in his iPhone and try to `sign in with apple` again in his iPad.
I don't want him to "sign in with apple" twice by editing his name or checking share or hide his email twice when he touch signIn Button.
I found out some codes from sample code that can help my scenario above
func performExistingAccountSetupFlows() {
// Prepare requests for both Apple ID and password providers.
let requests = [ASAuthorizationAppleIDProvider().createRequest(),
ASAuthorizationPasswordProvider().createRequest()]
// Create an authorization controller with the given requests.
let authorizationController = ASAuthorizationController(authorizationRequests: requests)
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
And I found out ASPasswordCredential will help me define credential user
First, should I implement saving userIdentifier in iCloud Keychain? Then should I need to add Keychain group Capability?
First, I don't really know where to put that performExistingAccountSetupFlows function in. I would like to present AuthorizationController when user touch button. So I tried this approach.
#available(iOS 13.0, *)
#objc private func handleAuthorizationAppleIDButtonPress() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
appleIDProvider.getCredentialState(
forUserID: KeychainItem.currentUserIdentifier ?? "") { [weak self] (credentialState, error) in
guard let `self` = self else { return }
log.debugPrint(KeychainItem.currentUserIdentifier ?? "nil")
switch credentialState {
case .authorized:
// The Apple ID credential is valid. Show Home UI Here
// MARK: Existing iCloud keychain
self.performExistingAccountSetupFlows()
break
case .revoked, .notFound:
let request = appleIDProvider.createRequest()
request.requestedScopes = [
.fullName,
.email
]
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
break
default:
break
}
}
}
And this doesn't work which I intended.
Is there any kind teacher who can answer my question?
Thank you.
You don't need to do anything. Apple handles it all for you by associating your app bundle with the identity in the user's iCloud.
If they run your app on another device that has the same iCloud account then they are not given an opportunity to create a new account when they use "Sign in with Apple" - They will simply be prompted to sign in with their existing account.
You can optionally use the code in performExistingAccountSetupFlows() to prompt the user to log in without needing to tap the "Sign in with Apple" button;
When this code runs, if an existing account association is found they will be prompted to authenticate. If no account is found then nothing happens.

When I use the apple to log in, the selection box will pop up. I choose to use the password to continue and the prompt is not complete

iOS13 (beta) Apple Login error
#available(iOS 13.0, *)
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// Handle error.
crprint(error.localizedDescription)
}
Failed to complete operation. (com.apple.AuthenticationServices.AuthorizationError error 1000.)
I've encountered the same issue yesterday and I've managed to fix it following these steps:
Go to https://appleid.apple.com/account/manage, under the Devices section you should find devices on which you are signed in with your Apple ID,
Find device/simulator on which Apple SSO is not working, click on it and click remove from the account,
Go back to your device/simulator settings, it will ask you to authenticate again. When you successfully authenticate, Apple SSO should work again!
I'm not sure what caused this issue, probably some issue between the simulator and Apple ID.
In my case, launching ASAuthorizationController including a request for ASAuthorizationPasswordProvider was causing the error.
Failed to complete operation. (com.apple.AuthenticationServices.AuthorizationError error 1000.)
From the ASAuthorizationError.Code documentation; 1000 is for unknown
ASAuthorizationError.Code.unknown
The authorization attempt failed for an unknown reason.
Declaration
case unknown = 1000
Ref: https://developer.apple.com/documentation/authenticationservices/asauthorizationerror/code/unknown
Now that's not particularly helpful but did give me a clue to check my ASAuthorizationController setup which I was trying to launch with 2 requests from ASAuthorizationAppleIDProvider & ASAuthorizationPasswordProvider, like so:
func loginWithAppleButtonPressed() {
let appleSignInRequest = ASAuthorizationAppleIDProvider().createRequest()
appleSignInRequest.requestedScopes = [.fullName, .email]
let anySignInRequest = ASAuthorizationPasswordProvider().createRequest()
let controller = ASAuthorizationController(authorizationRequests: [appleSignInRequest,
anySignInRequest])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
I tried this on a simulator that had an Apple ID with 2FA enabled and also on a device with another Apple ID without 2FA, and both times it would just go to authorizationController(controller:didCompleteWithError error:) and that's it.
Solution:
So to keep it simple, I launched ASAuthorizationController with only ASAuthorizationAppleIDProvider like so:
func loginWithAppleButtonPressed() {
let appleSignInRequest = ASAuthorizationAppleIDProvider().createRequest()
appleSignInRequest.requestedScopes = [.fullName, .email]
let controller = ASAuthorizationController(authorizationRequests: [appleSignInRequest])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
And voilà! This time things worked as expected:
When using an Apple ID with 2FA
popped up with the login request
When using an Apple ID without 2FA
popped up an error telling me to enable 2FA
called authorizationController(controller:didCompleteWithError error:) with error 1000
So seems that in my case ASAuthorizationPasswordProvider was the culprit but since ASAuthorizationError.Code.unknown is a generic error case, this solution may not work for you.
Also, In my case I need only ASAuthorizationAppleIDProvider for Apple ID sign in so dropped the support for ASAuthorizationPasswordProvider.
In my case i needed to first check ASAuthorizationPasswordProvider, then, if there are no stored credential, use ASAuthorizationAppleIDProvider. For this case i had to make some crunches. Code below:
// Initial point
public func fire(appleIDCompletion: #escaping AppleIDServiceCompletion) {
self.completion = appleIDCompletion
let requestPassword = ASAuthorizationPasswordProvider().createRequest()
performRequest(requestPassword)
}
// help function
private func performRequest(_ request: ASAuthorizationRequest) {
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
// delegate
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
if let e = error as? ASAuthorizationError {
switch e.code {
case .canceled:
trace("User did cancel authorization.")
return
case .failed:
trace("Authorization failed.")
case .invalidResponse:
trace("Authorization returned invalid response.")
case .notHandled:
trace("Authorization not handled.")
case .unknown:
if controller.authorizationRequests.contains(where: { $0 is ASAuthorizationPasswordRequest }) {
trace("Unknown error with password auth, trying to request for appleID auth..")
let requestAppleID = ASAuthorizationAppleIDProvider().createRequest()
requestAppleID.requestedScopes = [.email, .fullName]
requestAppleID.requestedOperation = .operationImplicit
performRequest(requestAppleID)
return
} else {
trace("Unknown error for appleID auth.")
}
default:
trace("Unsupported error code.")
}
}
completion?(.rejected(error))
}
Works like a charm 🔥
Simply Add + "Sign In with Apple" from Capability.
I've resolved it by adding sign in with apple as key in entitlements plist .
From Apple's example,
performExistingAccountSetupFlows, only call this method once on viewDidAppear. If user info exists already then Apple will show it to login. If not then it will throw error.
handleAuthorizationAppleIDButtonPress, whenever user taps on Sign in with Apple button, note that if an account already had existed it would have shown it to the user already. I believe its still in progress and not all use cases are covered, for example if user sees the login info initially from ViewDidAppear call and cancels it then user have to create a new account when tapping on this method since its missing ASAuthorizationPasswordProvider request. If user had some login info then in that case this call (with ASAuthorizationPasswordProvider) will succeed but if no data is available then user will not see any action on tapping this button since it will throw error.
I am still figuring this out, if I have anything more to add then I will update the answer. So, for now we can only have this one use case to use this Sign in with Apple option.
Update:
Once I created a new account, I was offered by this same flow to login with the already existing account. So, I can say that there is no need to include call to ASAuthorizationPasswordProvider request in handleAuthorizationAppleIDButtonPress method. I am doing all the testing on device.
You can always go to Settings -> AppleId -> Password & Security -> Apple ID Logins to check and delete account if you need to test various scenarios.
Update 2:
Everything seems to work fine in other scenarios too if you already have a saved password or App Id account created, so even if I pass ASAuthorizationPasswordProvider in the handleAuthorizationAppleIDButtonPress call, it is working fine. I would suggest to not pass ASAuthorizationPasswordProvider in the next call and keep the flow as described above, this way if no saved password is present or Apple Id created then it will provide option to the user to create a new id, if there is already an id that exists then it will show that id.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
performExistingAccountSetupFlows()
}
func performExistingAccountSetupFlows() {
// Prepare requests for both Apple ID and password providers.
let requests = [ASAuthorizationAppleIDProvider().createRequest(),
ASAuthorizationPasswordProvider().createRequest()]
// Create an authorization controller with the given requests.
let authorizationController = ASAuthorizationController(authorizationRequests: requests)
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
#objc
func handleAuthorizationAppleIDButtonPress() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
I resolved this by holding my finger down on finger print scanner til completion. I'm not an iphone user so I'm not used to the finger print scanner. If you pull your finger off too soon you get this error.

Is there a way to check push notification setting is ever requested?

Is there a way to check app has ever acquired push notification permission by system alert?
UIApplication.sharedApplication().currentUserNotificationSettings()?.types.rawValue
It returns 0 both when app never requested push notification permission and when user declined permission.
I want to show the system alert popup when app never requested permission. Also I want to let user go to setting to control permission setting if user ever declined permission.
For example:
let pushNotificationPermissionNeverAcquired: Bool = ???
if (pushNotificationPermissionNeverAcquired) {
// show system popup to acquire push notification permission
UIApplication.sharedApplication.registerForRemoteNotificationTypes([ * some types ])
} else if (UIApplication.sharedApplication().currentUserNotificationSettings()?.types.rawValue == 0) {
// user declined push notification permission, so let user go to setting and change the permission
UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString))
} else {
// push notification is allowed
}
How can I check pushNotificationPermissionNeverAcquired?
This is how I am doing it right now.
if NSUserDefaults.standardUserDefaults().stringForKey(NOTIFICATION_PERMISSIONS_ASKED) == nil
{
NSUserDefaults.standardUserDefaults().setObject("true", forKey: NOTIFICATION_PERMISSIONS_ASKED)
// Request permissions
}
else
{
// Show your own alert
}
You could save a Boolean too if you want and access it with 'boolForKey', it returns false if no key was found.
Forgot this as well.
if let enabledTypes = UIApplication.sharedApplication().currentUserNotificationSettings()?.types {
switch enabledTypes
{
case UIUserNotificationType.None:
// Denied permissions or never given
default:
// Accepted
}
}

Contacts framework doesn't request for access when needed

This is what I have so far:
private let contactStore = CNContactStore()
private func requestAccessForContacts() {
switch CNContactStore.authorizationStatusForEntityType(.Contacts) {
case .Denied, .NotDetermined:
contactStore.requestAccessForEntityType(.Contacts, completionHandler: { access, error in
if access {
print("super")
} else {
print("problem")
}
})
case .Authorized:
print("ok")
case .Restricted:
print("restricted")
}
}
The problem is printed on console, but nothing is displayed on screen, no request for access. Why?
Your code works; I was able to replicate the problem by first rejecting access to contact and re-running the app.
Once you have disallowed access, subsequent running of the app would log "problem" without displaying any request window.
If one wanted to see this request window again after denying, one could,
however, go to
"Settings" -> "Reset" and click "Reset Location And Privacy" in the simulator.
After having done so, the next time you run the app, a request window would pop up again.

TouchID canceling should dismiss the view controller

For my app I need to save one page of it with TouchID. So the user is kinda forced to use TouchID or if the device does not support it, a password. If the user cancels the TouchID authentication, I want to View to disappear and get back to the root view. I already had that working, but somehow it does not work anymore and I really don't know why?! I just copied until the canceled option, the rest is does not matter I guess.
func authenticateUser() {
let context = LAContext()
var error: NSError?
let reasonString = "Authentication is needed to access your App"
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error){
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success, policyError) -> Void in
if success {
print("authentification successful")
}
}else{
switch policyError!.code{
case LAError.SystemCancel.rawValue:
print("Authentification was canceled by the system")
case LAError.UserCancel.rawValue:
print("Authentication was canceled by user")
self.navigationController?.dismissViewControllerAnimated(true, completion: nil)
//Yes I also tried popToRootViewController, still doesn't work
}
The documentation for the evaluatePolicy call says:
"Reply block that is executed when policy evaluation finishes. This block is evaluated on a private queue internal to the framework in an unspecified threading context."
So the problem is that you are trying to call navigation from the wrong thread. You need to make that call on the UI thread. For example:
dispatch_async(dispatch_get_main_queue()) {
// Navigation goes here
}

Resources