-
Notifications
You must be signed in to change notification settings - Fork 223
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
How to revoke tokens during account deletion? [Apple Policy deadline June 30 2022] #282
Comments
Hi there! The module does not support it currently I see two paths to implement this:
I would love to see a PR implementing option 2 🙏 🙏 and would collaborate on it. I'm not sure if I'll have time prior to then to implement it, or if I do it will be closer to the deadline so may not offer enough time for others to get it in their app version review internally and out the door by June 30. It's also possible that in review the reviewers may have no way of knowing whether the token deletion requests are happening or not. So you may be able to pass review even without token revocation as long as you call the logout operation here during delete account. I have zero evidence one way or the other how they will enforce this during review, so unless someone else has evidence either way we will have to see if apps are rejected until/unless the token revocation is implemented here |
A lot of the community is concerned about this upcoming requirement (by community, I mean iOS developers all around - regardless of tech stack). I would not gamble with the apple review process. The purpose of token revocation is to remove associations to a developer's app from a user's 'Apps using Sign In With Apple' settings. If the token revocation is successful, you should be able to see that the user's setting no longer holds an association to the app. I suspect that reviewers will be able to do a basic test to verify this. I noticed in App Review that my reviewers were both creating and deleting accounts in my app (even before the requirement has become relevant). I have tried to implement this natively, but have not had success. Another option, which I have not tried, is to make a call to my backend service (custom function living in Firebase), but that would not be an elegant way of handling this. Apple has done a poor job at documenting this, other than outlining the requirement itself, and showing some curl HTTP examples. If you want to be in tune with others struggling with this requirement, including myself, please see my post below. I hope all of us from across different tech stacks can overcome this requirement. I am aware that Firebase is also looking to implement a custom solution to this, but it remains unclear when or how we'll be able to access it, if at all. Fingers crossed. |
I wouldn't want to personally gamble either, my preference is as stated:
You state:
Looks like this is what you mean? firebase/firebase-ios-sdk#9906 (comment) |
This appears to have a working solution for some but requires a fair bit of documentation on how to set up the JWT etc, and an implementation here of the code sketched out in the solution: https://stackoverflow.com/a/72656672/9910298 |
@mikehardy The missing part in that solution is how to get I suppose that Firebase at some point calls |
Well, Firebase is actually separate from this module, right? I mean obviously I'm firebase-interested, as maintainer over there at react-native-firebase but you may use this module without it. That implies that we should have a way to get the access_token and refresh_token right? Perhaps via re-authentication here? This will be called https://developer.apple.com/documentation/authenticationservices/asauthorization?language=objc here react-native-apple-authentication/ios/RNAppleAuthentication/RNAppleAuthASAuthorizationDelegates.m Line 43 in 9040e0e
We get an authorization code from that I believe? Now - with that, I think you can obtain a refresh token if you HTTP POST the authorization code along with app configuration secrets here https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens Now you've got a refresh, which you may invalidate per token revoke docs associated with this issue / that stackoverflow post Open question: I guess firebase servers have their own refresh token for the firebase / apple sign in integration, so: when you get a new refresh token associated with a specific user, does that invalidate the old one? And then if you invalidate the new refresh token, are we all clear? (it may be necessary to use the new refresh token to obtain an access token first - it appears based on a quick scan of related libraries that old refresh tokens are revoked by identity providers when new refresh tokens are issued, but frequently only after a grace period or a first use in order to give grace to mobile application environments where network connection failure may mean a refresh token request is issued but connection breaks before new refresh token is received) If so, we're set. If not, we need firebase to allow us to get the existing refresh token / access token they have, somehow ? |
This is a possible work around, if we follow the same steps it may work out: |
I don't think there needs to be a workaround based on my investigation in comment above.
Now make a new API in javascript that POSTs to the revoke API with either or both of the access token and refresh token and make a button that invokes it Then you can test if making new refresh tokens invalidate old refresh tokens, and if revoking the refresh token then removes the app from the accounts token list as visible at "the apple id binding information is deleted under Apps Using Apple ID of Settings" per the stack overflow comment we're all linking to above If it does, we're literally done here, solution implemented. If not then we know we have a hard block on getting access to whatever the existing refresh / access tokens are for whoever is paired with this library in practice (for example, react-native-firebase / firebase, or flutterfire / firebase etc) |
@mikehardy ouch, sorry, I indeed missed that this repo isn't actually related to Firebase. :) I'm definitely missing something what happens during Apple Sign In + Firebase Authentication. It looks like with Firebase we don't use So can Firebase generate any refresh tokens without |
btw, storing
as mentioned here https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens |
I was just talking about storing authorization codes for display / app restart purposes (in case it restarts? like you hot reload the code?) during this type of testing - in other words for near immediate use I am also a little vague on exactly how firebase-auth gets what it needs. The vaguery is associated with what exactly identityToken is - perhaps it is the refresh token ? or may be used as such? Generating a token only to revoke it may seem weird but if generating a new refresh token (that you control and may now revoke) has the side effect of revoking all other associated refresh tokens which may not be in your control (because they are off in the firebase cloud or something) then it sure would be a nice side effect, and all the sudden no longer weird, but crucial to get control of the tokens back for full revocation |
Indeed in react-native-firebase we send the identity token in to the OAuth provider along with the nonce but that's it. } else if ([provider compare:@"apple.com" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
credential = [FIROAuthProvider credentialWithProviderID:provider
IDToken:authToken
rawNonce:authTokenSecret]; what they are doing with it, I'm not 100% sure. It may be that they never actually create Apple refresh/access tokens, it may be that they decode the token in order to validate it, then assuming it is valid they simply trust it as a basis for emitting firebase (not apple) auth tokens. It may be that they are creating an apple refresh token etc via apple REST API though how they would do that with only the identity token and nonce, and not the authorization code I have no idea. All of this just needs experimentation I guess. |
There is an experimental result that the speculated path of "re-authorize user to get authorizationCode + use authorizationCode to get refresh-token + revoke refresh-token" works, with code linked So what we need now is a PR here, and it seems the sketch above should serve. I have attempted to research it and post ideas so that it was clear what sort of thing we need here and thus allow me to be a good collaborator and merge things but I need to set expectations clearly: I have no time do the code + testing required to get a working solution. Someone interested in this functionality is going to have to step up and implement it + test it. I will continue to be available for collaboration + merge + release though, you won't be on your own, I just don't have time for the code+test portion. |
Server Side
Also you can refer to this guide I followed the instructions to get my On my case on our team we are using GraphQL to communicate our App with the Server but the logic is the same if you are using REST, I added 2 queries and 1 mutation: queries: query GetAppleAuthClientSecret{
appleAuthClientSecret {
clientSecret
}
}
query AppleAuthRefreshToken($authorizationCode: String!, $clientSecret: String!){
appleAuthRefreshToken(authorizationCode: $authorizationCode, clientSecret: $clientSecret){
refreshToken
}
} mutation: mutation RequestAppleLoginRevocation($refreshToken: String!, $clientSecret: String!){
requestAppleLoginRevocation(clientSecret: $clientSecret, refreshToken: $refreshToken){
id
email
}
} behind sceneApple API Calls Get the Refresh Tokenrefers to refers to https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens curl -v POST "https://appleid.apple.com/auth/token" \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'client_id=CLIENT_ID' \
-d 'client_secret=CLIENT_SECRET' \
-d 'code=CODE' \
-d 'grant_type=authorization_code' Revoke Tokenrefers to https://developer.apple.com/documentation/sign_in_with_apple/revoke_tokens curl -v POST "https://appleid.apple.com/auth/revoke" \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'client_id=CLIENT_ID' \
-d 'client_secret=CLIENT_SECRET' \
-d 'token=REFRESH_TOKEN' \
-d 'token_type_hint=refresh_token' Client Side:I added a option to request the account deletion, when users press the button I call // call the GetAppleAuthClientSecret query
const clientSecret = ...
appleAuth.performRequest({
requestedOperation: AppleAuthRequestOperation.LOGOUT,
})
.then(response => {
const authorizationCode = response.authorizationCode;
return refetchToken({
authorizationCode: authorizationCode ?? '',
clientSecret: clientSecret,
});
})
.then(response => {
const refreshToken =
response?.data?.appleAuthRefreshToken?.refreshToken;
refreshToken &&
revokeAccess({
variables: {
clientSecret: clientSecret,
refreshToken: refreshToken,
},
});
}); The appleAuth performed request to LOGOUT users opens a modal (like when we call LOGIN), and it returns the authorization code if is executed successfully. We will use this to get the refresh token (need to mention that this is the token we need to revoke). The client secret just I said before is the JWT token signed using the Apple Authentication Certificate so, we can generate it without any parameter. So:
once we have these parameters we can call the Also we can use the listener provided by this library |
Okay, so this sounds like it's an "understood" problem technically, but we still need a PR here that will implement it given the correct configuration (certs and JWTs etc)? Or @cresenciof are you implying that there is no way to do this in this module / on device and it requires a server running? I was under the impression we could do this in the app if we had the right things configured and called the right REST APIs ? |
@mikehardy You're right, we can call this REST API's directly from the client. The only thing required is the It gives us two ways to work around it:
|
From the perspective of a developer where cloud functions cost $ and set up time + reliability concerns + it's own updates etc but an update every 6 months is almost free, this seems like a reasonable solution and would work well Might even be possible (as an enhancement, once it was working) to do console.warn when the secret was approaching expiration etc. Assuming that works I think it would be a fantastic solution, all self-contained here in the module after initial configuration. Given a deadline of just a few days - even if it is not fantastic in everyone's opinion - it would at least provably work and not add any external server requirements for people |
Using |
Also, regarding exposing client secret to client side. Am I right that that jwt isn't issued strictly to a user you're authenticating via Apple Sign In? In theory an attacker can take hold of it and somehow use it for some operations on behalf of other users (?) I don't know too much about the details here, but my intuition is that exposing it should be avoided if possible (and it's possible in our case as far as I can see). |
@algrid I think removing the need to store a refresh token is a positive, and it appears that LOGOUT will prompt a user interaction the same as LOGIN so the user experience has the same number of interactions. On balance then this looks better than storing a token As for exposing the JWT representing the client secret, I think this would potentially let an attacker perform "sign in with apple" API calls as your Apple Developer account / app combination. The current set of those is sign in / sign out / revoke-token I think. These will prompt user interaction for sign in / sign out at least but it may be possible to spoof yes. As with all things related to security: think very critically about what you are protecting (value of successful attack) what it costs to defeat any protection (cost of attack) and act according to your tradeoffs. I think the cost of attack is reasonably low here as an app can be decompiled, you have to assume the JWT is recoverable+recovered. What is the value - hard to say. Someone is now associated (or disassociated?) with your app (not even the spoofiing app?). I'm not sure that has value to anyone? I always assume I'm missing something when I analyze security cost/benefit though so I'll happily learn something if I'm wrong. |
@mikehardy wouldn't it be the most frequent use case when a user is already signed in at the point when account deletion is triggered? At least that's true in my case. I show a 'delete account' button only when I have a user, otherwise it doesn't make sense. So, having to authenticate for logout requires more actions from the user. |
@algrid I agree with you. I also only show delete on a screen that is past my login gate. At the same time, I'm unaware of any way to get authorization code (which you can then escalate to refresh token) without triggering some authentication interaction with the user. I would definitely de-compose the activities in any PR here (or local work) such that one chunk was a) "do we have a refresh token? if not let us get one via login (or logout) to get authorization code then use that to get refresh token" ...and then b) "okay let us use that refresh token plus all our other magical config like JWT etc to revoke tokens" And the "magical config" part could be a further step where for those comfortable with the risk they could just config the JWT in the app (exposing themselves to spoofed auths if I understand) or it could be an API fetch to a server that generates them with short expiry |
Thank you for dropping this solution @cresenciof, we tried to do the same but we are getting an invalid_client error response on the generate auth token endpoint, regardless of the error(even if you drop an empty body you'll get the same), although we followed this documentation to generate the client_secret and still not working. Any suggestions? |
@AhmadMazaal try first to use Postman or another REST client to rule out some problem related to the CORS client/server configuration, I received this error some other time but in my case it was sending an incorrect |
@AhmadMazaal I would also re-check your jwt fields and that you're using correct p8 key and correct signing algorithm. I implemented a similar flow (my GCFs are in Python) and it works. |
@mikehardy This argument about account deletion being a 'sensitive' operation actually makes sense: firebase/firebase-ios-sdk#9906 (comment) So yeah, I think re-authenticating user for that is the best way of doing it. And it's convenient to not have to store a refresh token. :) |
Thank you for this suggestion, I tried the same request for both endpoints and it worked pretty well on Postman. The problem was with us using Axios, it was always serializing the body to multipart/form-data instead of application/x-www-form-urlencoded, although it was included in the header. Found the solution in this stackoverflow question. It should look something like this
Also found this helpful tutorial from MongoDB to generate the CLIENT_SECRET Hope it helps anyone struggling with the same |
@cresenciof After doing all the above successfully, the app is still saved in the sign in settings of Apple and was not removed, we are using our backend and Firebase to save user data, is that related? |
@AhmadMazaal it sounds like the token revocation has not gone exactly as planned. Note that saving data in any persistent location related to the user is orthogonal that is, it is a separate-but-related issue. You are responsible for deleting any related user data per Apple requirements (stated differently: they have certain categories they allow you to maintain such as data you must retain for legal reasons - you are subject to their requirements and should be familiar with them and should delete all related data per their requirements) |
@mikehardy Indeed you are right, thank you for the reply. It appears that we had a typo in the token property of the revokeAuthTokenBody body. I will edit the previous comment match the working solution |
Can anyone confirm that apple token revoke also remove app from apple allowed to signIn list? |
@Romick2005 yes, it's confirmed several times in the related firebase issue, firebase/firebase-ios-sdk#9906 (comment) It appears now that people are able to pass App Store Review with the cloud-function-based solution linked/recommended in that solution and now it's down to some small items like "if the user deletes account and we revoke token, now we don't seem to get name + email if they register again post-delete", which is perhaps a separate issue |
Hello - sorry to pester in multiple threads! I'm trying to understand how to make this work for our app: I'm planning to reauthenticate with this method at the point the user attempts deletion. I guess as it stands I can't use this lib for any of this process now that the key is required? How is it that this lib authenticates without a key anyway out of interest? Thanks |
Hey there - yeah I saw your other post on firebase-ios-sdk, this stuff is tricky yes. If by key you mean the p8 that's supposed to be generated/downloaded from Apple developer console for the app, then uploaded to firebase project config for apple auth, if you don't have that I am also confused. I thought that was a fundamental requirement. As such, if it is working without that I'm not sure how? And I'm not sure how you can use the sign in with apple REST API without it as I believe it is required to generate the JWT. Please note two things though: 1) I have not had time to implement this myself so I've been active here but just listening+thinking and that's no substitute for actually doing it myself so I know, and 2) I'm traveling now so apart from this comment I won't have time myself to actually do it for my apps yet either so I'm not the most useful at the moment, apologies |
Hello!
Here we have another way to generate the The keys' location can be found here . |
I did this in my project which is using express backend and this npm package in react native.
Setting env: I'm using it like in ts file:
Here I'm signing the private p8 key. You need to expose this from a route.
I used api call to get
|
so if we implement this, are you able to get email and full name when user login to try again? |
for those who are getting error code "invalid_client" with axios make sure to not use new URLSearchParams |
Are these implementations no longer needed? Has it been resolved with the code mentioned in the RN Firebase docs (attached below)?
11 September Edit: revokeToken is not a function available in the library. I'm not sure why it is being mentioned in the docs, and used in the sample code? |
For those of you that face this issue again in the future and are using firebase for apple sign in, this might help you: invertase/react-native-firebase#7239. Follow the Test Plan mentioned, and make sure the library version for react-native-firebase/auth is 18.3.0^. |
This looks like something that could do with a documentation patch PR proposal here for the case of using firebase auth and the case of not using firebase auth. Anyone that could post a PR for either or both would be my hero |
According to apple docs, apps will need to provide an option to delete user account by June 30, 2022.
When signing-in via apple the app also needs to revoke the user tokens as mentioned in the FAQs on the same page and as documented here.
How can we revoke user token using this library? Does v2.2.1 support revoking user token? Is it integrated in the logout flow or it that a different API method ?
Library version - v2.2.1
The text was updated successfully, but these errors were encountered: