Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logout launching Apple Sign In process #10

Closed
hangindan opened this issue Dec 11, 2019 · 57 comments
Closed

Logout launching Apple Sign In process #10

hangindan opened this issue Dec 11, 2019 · 57 comments
Labels
apple-issue bug Something isn't working help wanted Extra attention is needed

Comments

@hangindan
Copy link

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

@Salakar
Copy link
Member

Salakar commented Dec 11, 2019

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

@hangindan
Copy link
Author

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

@hangindan
Copy link
Author

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)))
  })

@mikehardy
Copy link
Collaborator

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

@mikehardy
Copy link
Collaborator

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": "[email protected]",
  "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": "[email protected]",
      "providerId": "password",
      "uid": "[email protected]",
      "photoURL": "https://platform-lookaside.fbsbx.com/platform/profilepic/?asid=xxxx&height=100&width=100&ext=1577401961&hash=xxxx",
      "displayName": "xxx xxx"
    },
    {
      "email": "[email protected]",
      "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": "[email protected]"
    }
  ],
  "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?

@mikehardy mikehardy pinned this issue Dec 14, 2019
@mikehardy mikehardy added bug Something isn't working help wanted Extra attention is needed labels Dec 14, 2019
@Salakar
Copy link
Member

Salakar commented Dec 16, 2019

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

@mikehardy
Copy link
Collaborator

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

@mikehardy
Copy link
Collaborator

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?

@Salakar
Copy link
Member

Salakar commented Dec 23, 2019

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

@Salakar
Copy link
Member

Salakar commented Dec 24, 2019

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

@eminsr
Copy link

eminsr commented Dec 25, 2019

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.

@mikehardy
Copy link
Collaborator

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

@brascene
Copy link

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..

@Estebank94
Copy link

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

@mikehardy
Copy link
Collaborator

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

@lucasferreiraestevam
Copy link

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

@mikehardy
Copy link
Collaborator

@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

@kevwang19
Copy link

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

@mikehardy
Copy link
Collaborator

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

@kevwang19
Copy link

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
Copy link

@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 :(

@mikehardy
Copy link
Collaborator

@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 :-)

@trev91
Copy link

trev91 commented Feb 15, 2020

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!!

@mikehardy
Copy link
Collaborator

so next step is to contact apple support.

Current status. Please give it a shot and report back

@Desintegrator
Copy link

Desintegrator commented Mar 1, 2020

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

@mikehardy
Copy link
Collaborator

@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

@OmarBasem
Copy link

Any news on that issue?

@ajw-m
Copy link

ajw-m commented Mar 12, 2020

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

@twelve17
Copy link

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

@ajw-m
Copy link

ajw-m commented Mar 17, 2020

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.`

@mikehardy
Copy link
Collaborator

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

@ajw-m
Copy link

ajw-m commented Mar 17, 2020

🤔 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
Copy link

ajw-m commented Mar 20, 2020

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
Copy link

mralj commented Apr 9, 2020

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! 🤞

@mikehardy
Copy link
Collaborator

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

@Return-1
Copy link

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

@LMestre14
Copy link

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?

@andreialecu
Copy link

andreialecu commented Jul 15, 2020

@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

@mars-lan
Copy link

mars-lan commented Jun 7, 2021

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

@mikehardy
Copy link
Collaborator

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.

@houmie
Copy link

houmie commented Jun 26, 2022

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
Copy link

mralj commented Jun 26, 2022

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 :)

@houmie
Copy link

houmie commented Jun 26, 2022

Thank you very much.

@zell180
Copy link

zell180 commented Apr 11, 2023

Someone implemented working logout on their app?

@vijaykumariorbit
Copy link

Anyone having working Logout code on their app?

@anatooly
Copy link

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".

@Romick2005
Copy link

Not manually, just not implemented by you!

@sujathasperi2022
Copy link

Has anyone able to make a workaround for the logout?

@neeleshyadav253
Copy link

Clear Local Data: Simply remove the user data stored in your app, such as tokens, user information, etc.
Credential State: Optionally, check the user's credential state using getCredentialStateForUser to ensure that the credentials have been revoked or are no longer valid.
Implementing Logout:
Given the limitations, here's how you can implement a more reliable logout process:

import { appleAuth } from '@invertase/react-native-apple-authentication';
import { auth } from '@react-native-firebase/auth';
import { useDispatch } from 'react-redux';

export const userAppleSignOut = () => async dispatch => {
try {
// Optionally check the credential state before logging out
const credentialState = await appleAuth.getCredentialStateForUser(
auth().currentUser?.providerData.find(provider => provider.providerId === 'apple.com')?.uid
);

if (credentialState === appleAuth.State.AUTHORIZED) {
  console.log('Apple credentials are still authorized.');
} else {
  console.log('Apple credentials are no longer valid.');
}

// Clear Firebase session if using Firebase
await auth().signOut();

// Clear any local user data or session information
dispatch(clearUserData());

// Dispatch success action
dispatch(AppleSignOutSuccess());

} catch (error) {
console.error('Apple Sign-Out Error:', error);
// Handle error accordingly
}
};

Explanation:
Credential State Check:
This part of the code checks whether the user's Apple credentials are still authorized. This is optional but can be useful to determine the current state of the user's credentials.

Firebase Sign-Out:
If you're using Firebase Authentication, the auth().signOut() method signs out the user from Firebase.

Clear Local Data:
The clearUserData action should remove any stored user data in your app, effectively logging the user out from the app's perspective.

No Apple LOGOUT Operation:
As discussed, directly calling the LOGOUT operation from Apple is not recommended due to the issue of triggering the sign-in dialog again. Instead, managing logout locally by clearing data and signing out from your backend (if applicable) is the preferred method.

Conclusion:
The correct approach to handle Apple Sign-In logout is to manage it within your app by clearing the necessary data and handling the session management yourself. This approach avoids triggering the unwanted Apple Sign-In dialog and aligns with the best practices observed in the community.

@ftaibi
Copy link

ftaibi commented Oct 25, 2024

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?

How do you comply with GDPR on the sign up? do you add a checkbox?

@DenisDov
Copy link

AppleAuthRequestOperation.LOGOUT doesn't exist in the API. When implementing Apple Sign In logout, you only need to clear your app's local state, which is already handled by your other logout operations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
apple-issue bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests