invertase/react-native-apple-authentication

Logout launching Apple Sign In process

hangindan opened this issue Β· 55 comments

I'm testing this on a simulator with iOS 13.2.2

The Apple Sign In process is working (with Firebase). However when I call the Logout process the Apple Sign In dialog opens and must be canceled to complete logout process.

My logout code:

  const appleAuthRequestResponse = await appleAuth.performRequest({
    requestedOperation: AppleAuthRequestOperation.LOGOUT
  })
  .catch((error) => {
    console.log("Caught logout error..", error)
  })
 //Sign out of Firebase

When called the Apple dialog below is shown. The actual Sign out of Firebase code is not executed until user hits cancel on Apple dialog.

Not sure how to logout without opening dialog?

Simulator Screen Shot - iPhone 11 - 2019-12-11 at 11 14 41

I think you need to pass the Apple User ID also along with the request, this.

Thanks for quick reply. Noticed that as soon as I hit send. I'll give that a try.

I tried passing in user value (as well as with nonce) but I still get the Apple Sign In dialogue. Though it might have been related to simulator but just got same result on real device. Verified user matched response from login. Guessing it's just something simple I'm doing wrong, but haven't found it yet.

  const requestOptions = {
    user: appleUserID, //user string
    requestedOperation: AppleAuthRequestOperation.LOGOUT
  };
  //re-launches dialog until user cancels
  const appleAuthRequestResponse = await appleAuth.performRequest(requestOptions)
  .catch((error) => {
    return dispatch(logOutFailure(customError(error)))
  })

I confirm this locally - as per the docs https://github.com/invertase/react-native-apple-authentication/blob/master/README.md#4-implement-the-logout-process it pops the sign-in dialogue

Experimenting with sending in the user just to see, but at minimum it's a docs issue

Confirmed even when sending user in:

    try {
      console.log('UserStore::logout calling apple logout');
      if (AppleAuth.isSupported) {
        console.log(
          'UserStore::logout - user currently looks like: ',
          JSON.stringify(firebase.auth().currentUser, null, 2)
        );
        if (firebase.auth().currentUser?.providerData) {
          for (let i = 0; i < firebase.auth().currentUser?.providerData!.length; i++) {
            if (firebase.auth().currentUser?.providerData[i].providerId === 'apple.com') {
              console.log('UserStore::logout - found apple.com provider, trying logout');
              const appleAuthRequestResponse = await AppleAuth.performRequest({
                requestedOperation: AppleAuthRequestOperation.LOGOUT,
                user: firebase.auth().currentUser?.providerData[i].uid,
              });

              // get current authentication state for user
              const credentialState = await AppleAuth.getCredentialStateForUser(
                appleAuthRequestResponse.user
              );

              // use credentialState response to ensure the user credential's have been revoked
              if (credentialState === AppleAuthCredentialState.REVOKED) {
                console.log('UserStore::logout - apple credential successfully revoked');
              }
            }
          }
        }
      }
    } catch (e) {
      console.log('UserStore::logout - error from apple logout', e);
    }

I see this in console (redacted) then the login pops:

 UserStore::logout calling apple logout
 LOG  UserStore::logout - user currently looks like:  {
  "phoneNumber": "+x",
  "isAnonymous": false,
  "email": "kdev1@xxxxxxx.net",
  "refreshToken": "xxxxxxx-xxx-xxxx-xxxxx",
  "metadata": {
    "creationTime": 1568260253456,
    "lastSignInTime": 1576134039839
  },
  "uid": "xxxxxx",
  "photoURL": "https://platform-lookaside.fbsbx.com/platform/profilepic/?asid=xxxxxx&height=100&width=100&ext=1577401961&hash=xxxx",
  "providerId": "firebase",
  "emailVerified": true,
  "providerData": [
    {
      "email": "kdev1@xxxx.net",
      "providerId": "password",
      "uid": "kdev1@xxxx.net",
      "photoURL": "https://platform-lookaside.fbsbx.com/platform/profilepic/?asid=xxxx&height=100&width=100&ext=1577401961&hash=xxxx",
      "displayName": "xxx xxx"
    },
    {
      "email": "mike@xxxx.net",
      "providerId": "google.com",
      "uid": "xxxx",
      "photoURL": "https://lh3.googleusercontent.com/a-/xxxxx=s96-c",
      "displayName": "Mike Hardy"
    },
    {
      "providerId": "phone",
      "phoneNumber": "+xxxx"
    },
    {
      "providerId": "apple.com",
      "uid": "000420.xxxxxx",
      "email": "apple@xxxxxx.net"
    }
  ],
  "displayName": "xxxx Hardy"
}
 LOG  UserStore::logout - found apple.com provider, trying logout

So I think I'm doing everything correctly and I know I'm sending the user in, but it's not working for some reason?

Quick question, is it still emitting a credential revoked event? Wondering if it is revoking correctly but maybe because there's default scopes that its trigging the sign-in flow again

Not 100% but I had it on my list to alter the example to show this behavior, and to contrast current implementation with expo implementation to see how things went.

When I do that I'll check event emission as well

I received the credential revoked event.
I attempted to send requestedScopes: [] into the logout operation and that didn't help

Anything else I could try?

The logout operation enum is '3', can be seen here in JS:
https://github.com/invertase/react-native-apple-authentication/blob/master/lib/index.js#L35 and converted here to a native ASAuthorizationOperation enum in ObjC: https://github.com/invertase/react-native-apple-authentication/blob/master/ios/RNAppleAuthentication/RCTConvert%2BASAuthorizationAppleIDRequest.m#L61

Perhaps double checking those are coming through correctly, but I'm pretty sure they are. Only other thing I can think of is commenting out this block on the logout request and seeing what happens, perhaps if the requested scopes property is mutated it assumes login πŸ€·β€β™‚ : https://github.com/invertase/react-native-apple-authentication/blob/master/ios/RNAppleAuthentication/RCTConvert%2BASAuthorizationAppleIDRequest.m#L30-L34

Looked at this today and no matter what I tried it always shows the signin flow, even with a raw obj-c app, so I think this is a bug in Apple Auth, unless we're missing anything obvious

I've change the code and tried to logout with this native Xcode project from apple but it kept prompting the login screen. So I don't think the issue here related to this library.

Okay, so - anyone find or create an upstream issue :-)? I know I could do it myself but my dance card is pretty full at the moment with guests+travel over the holidays

Something weird is happening here, anyone figured it out? What's happening in my case is that LOGOUT is actually never fired, and here's the snippet:

try {
  const appleAuthRequestResponse = await appleAuth.performRequest({
    requestedOperation: AppleAuthRequestOperation.LOGOUT
  });
  const credentialState = await appleAuth.getCredentialStateForUser(
    appleAuthRequestResponse.user
  );
  console.log("Credential state ", credentialState);
  if (credentialState === AppleAuthCredentialState.REVOKED) {
    console.log("User is unauthenticated");
  }
} catch (appleLogoutError) {
  console.warn("Apple logout error: ", appleLogoutError);
}

This code just triggers the sign in process, and a result, the credential state is 1, which is LOGIN.
I hope someone will figure out this issue..

I am having the same issue, sign out always fires sign in process.

Correct there has been no progress here. Anyone that has time to follow next steps would win a pile of internet points

I am having the same issue, sign out always fires sign in process. :(

@lucasferreiraestevam great! Matches exactly what we know here. How did it go when you tried:

  • try the alternative implementation (and check their issue list) at expo/expo:packages/expo-apple-authentication@master to see if they have the same issue
  • if they have the same problem, look upstream (to be specific: apple developer support) to see if they have some issue being tracked related to this, and if not, open one to have them check it

Same issue here with logout, hope it gets addressed soon!

@kevwang19 me too! How did it go when you contacted apple about it?

Well for now I'm trying to work around it by using asyncstorage and my own server to handle the authentication logic. I don't know how the native stuff works, so unfortunately not sure if I can ask a meaningful question to Apple since I don't know what's going on under the hood of this library.

@lucasferreiraestevam great! Matches exactly what we know here. How did it go when you tried:

  • try the alternative implementation (and check their issue list) at expo/expo:packages/expo-apple-authentication@master to see if they have the same issue
  • if they have the same problem, look upstream (to be specific: apple developer support) to see if they have some issue being tracked related to this, and if not, open one to have them check it

-- implemented but to no avail :(

@lucasferreiraestevam just to be clear - you tried the expo implementation and had the exact same issue? If so, excellent! In the sense that we have likely eliminated implemenation as the problem, and it's an apple sign-in issue, so next step is to contact apple support. I am working on them with about different (after you enable apple sign-in you can't transfer an app) so I'm at my personal πŸ’© limit with apple support and will not be civil if I do it, so hopefully someone else has time :-)

Any updates on this? I'm looking to go live here pretty soon and would love to know if anyone has an update on what to expect as far as a timeline goes. Thanks!!

so next step is to contact apple support.

Current status. Please give it a shot and report back

same issue. What about logging out from app without apple request i.e just remove user data?

@Desintegrator great you've found this issue! Have you tried your suggestion? Do you have a request in with Apple for clarification and/or any status on it? That would move this forward

@Desintegrator great you've found this issue! Have you tried your suggestion? Do you have a request in with Apple for clarification and/or any status on it? That would move this forward

I didn't contact apple support. I just make logging out process without appleAuth usage. It seems like it works. But I'm not sure if Apple likes it.

@Desintegrator great you've found this issue! Have you tried your suggestion? Do you have a request in with Apple for clarification and/or any status on it? That would move this forward

I didn't contact apple support. I just make logging out process without appleAuth usage. It seems like it works. But I'm not sure if Apple likes it.

@Desintegrator I was able to pass the review process using this approach.

I can also confirm passing the review process with this, but no one has yet put in the time to get to the bottom of it unfortunately. Hopefully some kind soul will take it up with Apple officially - I'm stretched at the moment with module maintenance and don't have time myself.

I have tried out the example app available on the Implementing User Authentication with Sign in with Apple - Sample Code page, and I also have experienced this issue:

siwa-bug-demo-juice

I have submitted an issue on apple's https://feedbackassistant.apple.com site, and will report if I hear back.

@twelve17 you are the internet hero today πŸ† πŸ˜„ thank you!

@mikehardy let's see if Apple replies to my issue. It's my second "apple sign in" issue, the first one I've yet to hear back from and it was submitted in december 2019.

@trev91 @Desintegrator could you two also submit feedback to apple on their feedback assistant site? Maybe it will help put this on their radar.

Any news on that issue?

ajw-m commented

They seem to be avoiding this entirely in their demo app.
Their code just clears the keychain and puts you back in the home page.

(You need to test this on an actual device, the simulator works differently)

However, when you go back to the login page, the user's info (name, email) are null, because the user is already signed in.

Edit If anyone is interested in more research

Added the following snippet as a logout function to their test project in signOutButtonPressed

https://gist.github.com/ajw-m/5026597e77524b2d07cfd32446b6f8cc#file-logout-swift

It also pops up the login dialog

No news. If you are invested in a fix, please file a bug in apple’s feedback assistant.

ajw-m commented

Got a response from Apple.

`Okay. This behavior is by design; Sign in with Apple provides the mechanism to authorize a user, with their Apple ID, for a particular application. To remove access to an application, the user must manually revoke their credentials via the following support pageβ€”

https://support.apple.com/en-us/HT210426

Additionally, if you are trying to implement Sign in with Apple in your native application, please see the follow sample code project for additional informationβ€”

https://developer.apple.com/documentation/authenticationservices/implementing_user_authentication_with_sign_in_with_apple

Furthermore, you can check user credentials while your application is running, by either of the followingβ€”

  • ASAuthorizationAppleIDProvider.getCredentialState(forUserID:completion:)
  • NSNotification.Name.credentialRevokedNotification

Both of which can be used to update your application state based on the current credential state of the user.

Note: If the user is no longer authorized, simply log the user out of your application and/or server. If the user decides to authorize your app in the future, you may reuse the same information as before.

Please let me know if you have any further questions regarding Sign in with Apple.`

πŸ€” did that actually answer the question? I miss things all the time, but it did not seem like it answered the question.

ajw-m commented

πŸ€” did that actually answer the question? I miss things all the time, but it did not seem like it answered the question.

Didn't answer, I replied to their email with more questions. Just keeping everyone in the loop πŸ‘

ajw-m commented

Update:

Their library does not support "logout".
User has to manually "logout" from their settings.

Feel free to send me any specific questions you might have, I'll try to forward them to Apple

mralj commented

We are trying to implement this feature in our app ATM, we have't implemented this yet, so there is good chance I am completely wrong about this. Nonetheless, here are my 2 cents:

TL;DR;

I think, for the most apps, there is no need for "Apple Logout". As couple of you already mentioned above this is fine with Apple - apps have passed review process, and I think this is intended

"Apple Logout" being:

await appleAuth.performRequest({
    requestedOperation: AppleAuthRequestOperation.LOGOUT
  })

More info

Here is how I understood the process:

  1. When you do Sign in with Apple, they create Special Apple Account (let's call it SAA) just for your app and this account, most importantly, has: id (from reading the docs I think it is user), fullName and email
  2. You have to somehow/somewhere store this info (in their example it is stored in KeyChain)
  3. now you are "in the app" ... you do some stuff, and decide to Log Out
  4. Following Apples's example, they simply delete data stored in KeyChain
  // For the purpose of this demo app, delete the user identifier that was previously stored in the keychain.
        KeychainItem.deleteUserIdentifierFromKeychain()
        
        // Clear the user interface.
        userIdentifierLabel.text = ""
        givenNameLabel.text = ""
        familyNameLabel.text = ""
        emailLabel.text = ""
        
        // Display the login controller again.
        DispatchQueue.main.async {
            self.showLoginViewController()
        }

And that's it :)

What your app should do, is to handle Logout as if it would, if there was no "Sign in with apple" (and if you store SAA id delete this as well)

In our app, we consider user to be logged in if we have userToken and userId so we would delete those from memory / AsyncStorage, as well as SAA id

  1. Now let's say, user exits the app, they should again be presented with Login Screen (note in 4. we did not do "Apple logout", but w did Logout user).

How does Apple example do this check ?

   let appleIDProvider = ASAuthorizationAppleIDProvider()
        appleIDProvider.getCredentialState(forUserID: KeychainItem.currentUserIdentifier) { (credentialState, error) in
            switch credentialState {
            case .authorized:
                break // The Apple ID credential is valid.
            case .revoked, .notFound:
                // The Apple ID credential is either revoked or was not found, so show the sign-in UI.
                DispatchQueue.main.async {
                    self.window?.rootViewController?.showLoginViewController()
                }
            default:
                break
            }
        }

So if I am following logic correctly, KeychainItem.currentUserIdentifier is empty, and appleIDProvider.getCredentialState will result in case .revoked, .notFound

I suspect that we'll have something similar in our app, meaning that we (already do) check if there is userToken && userId, if not, user is logged out (I think for us, there will be no ned to perform appleIDProvider.getCredentialState check in this scenario)

Logged in check

This one is simple,

Apple example:

KeychainItem.currentUserIdentifier won't return empty, and calling appleIDProvider.getCredentialState returns .authorized.

For our app we'll probably firstly check for userId, token and if appleIDProvider.getCredentialState returns .authorized., if all 3 are true, user is logged in :)

And if you "Stop app from using Apple ID" - this would be as if your SAA was destroyed, and next time when you enter the app and try Apple Login, you would be getting new SAA and new user in the app.

What's more, even after "Apple logout", as other have reported I still get AUTHORIZED status, which goes in favour to my theory that apps should NOT use:

await appleAuth.performRequest({
    requestedOperation: AppleAuthRequestOperation.LOGOUT
  })

So why even have ASAuthorizationOperationLogout

My GUESS is that Apple wanted aligned interface between:

  • Sign in With Apple and
  • SSO (single sign-on)

In their AuthenticationServices framework

In the docs about AppleAuthRequestOperation it is stated that this is used both for AppleId and SSO

Use one of these values as the requestedOperation property in an OpenID request that you make with an instance of either ASAuthorizationAppleIDRequest or ASAuthorizationSingleSignOnRequest.

Unfortunately, there is no special info about LOGUT:

An operation that ends an authenticated session.

Whatever that actually means πŸ€·β€β™‚οΈ

If all my speculations & conclusions are correct, I think that it would be best to update this modules docs (point 4, regarding logout), so that using;

await appleAuth.performRequest({
    requestedOperation: AppleAuthRequestOperation.LOGOUT
  })

Is NOT encouraged, but custom logout solution is

Finally

I cannot stress this enough:

  1. I am by no stretch of imagination "expert" in this topic
  2. I haven't implemented this in our app
  3. I have no knowledge of iOS development

These are just my thoughts/observations after couple of hours docs reading and digging around the net :)

Hope this helps! 🀞

API not available / upstream "issue" really. Don't do signout, basically. Great information above ⬆️

In this scenario we need to update docs. I ll shoot for a PR soon-ish

The only problem with this is that without a "force logout" from the apple's authentication, the next time you log in with apple, the information about the FullName and Email come as null... Until now I had to store the apple id from the first apple login in the server in order for future logins to get the users info. But because of the gdpr I have to delete the users info from the database, that includes the apple id.

In order for the user to create a new account he can't do it through apple authentication since I don't receive the email information of the user (unless the user goes to "Password & Security" from his iphone and "forces" logout).

Is there any update regarding this?

@LMestre14 if you decode the identityToken JWT you can extract the email out of it. I think this is a recent change. Paste it into JWT.io and you will see the email in the payload.

For the name however there is no solution yet.

Ref: https://developer.apple.com/forums/thread/119826

The only problem with this is that without a "force logout" from the apple's authentication, the next time you log in with apple, the information about the FullName and Email come as null... Until now I had to store the apple id from the first apple login in the server in order for future logins to get the users info. But because of the gdpr I have to delete the users info from the database, that includes the apple id.

In order for the user to create a new account he can't do it through apple authentication since I don't receive the email information of the user (unless the user goes to "Password & Security" from his iphone and "forces" logout).

Is there any update regarding this?

FWIW there seems to be no solution to this: https://stackoverflow.com/questions/57545635/cannot-get-name-email-with-sign-in-with-apple-on-real-device

Extracting email from JWT only seems to work if it's not a forwarding address: https://developer.apple.com/forums/thread/121496?answerId=421415022#421415022

Good digging - so there will always be the possibility of a new user that is a "returning" user from apple auth perspective that is using a forwarding address so you can't JWT decode. I guess if using apple auth and email is mandatory you'll simply have to have to have a set of input email + confirm email + change email flows.

userToken

Hello @mralj , thanks for the great write up. It is clear to me how to obtain, fullName, email and userId. But what do you mean by userToken? Can do I obtain that to check if the user is logged in as you said? Thanks

mralj commented

userToken

Hello @mralj , thanks for the great write up. It is clear to me how to obtain, fullName, email and userId. But what do you mean by userToken? Can do I obtain that to check if the user is logged in as you said? Thanks

Hi, I phrased that part badly, sorry!
Not sure if anything has changed since we've implemented this (I was shocked to see it was almost 2yrs since my comment on this thread 😱), but AFAIK, no you cannot obtain it, at least not from Apple.

The userToken is simply a token for authentication&authorization. It is generated by our server each time the user registers/logins. If the mobile app doesn't have this token we know a user is not logged in.
On logout, we send a request to the server to delete the token for that particular user from our DB and we delete it from the mobile app's storage.

Hopefully, this answers your question :)

Thank you very much.

Someone implemented working logout on their app?

Anyone having working Logout code on their app?

Apple review need use firstName, lastName, email for auth process. First reg β€” fullName return, then user Delete Account (it's button need for Apple review), then when i secondary reg, fullName not returned because user not manual delete app from "Apps Using Apple ID".

Not manually, just not implemented by you!