react-native-apple-authentication icon indicating copy to clipboard operation
react-native-apple-authentication copied to clipboard

How to revoke tokens during account deletion? [Apple Policy deadline June 30 2022]

Open sushrut-desora opened this issue 2 years ago • 42 comments

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

sushrut-desora avatar Jun 08 '22 20:06 sushrut-desora

Hi there! The module does not support it currently

I see two paths to implement this:

  1. Until / unless this module has an implementation, you will need to implement a server feature somewhere that handles it. This is possible, and is completely under your control, but is not a great implementation path in my opinion

  2. implement a PR here follows those docs by using the information we have available within the library (regarding all the app information - client_id / client_secret / current token) to post to the URL in the docs and revoke the refresh and access token

I would love to see a PR implementing option 2 :pray: :pray: 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

mikehardy avatar Jun 08 '22 21:06 mikehardy

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.

https://stackoverflow.com/questions/72399534/how-to-make-apple-sign-in-revoke-token-post-request?noredirect=1#comment128385577_72399534

andrejandre avatar Jun 21 '22 18:06 andrejandre

I wouldn't want to personally gamble either, my preference is as stated:

  1. implement a PR here follows those docs by using the information we have available within the library (regarding all the app information - client_id / client_secret / current token) to post to the URL in the docs and revoke the refresh and access token

I would love to see a PR implementing option 2 pray pray and would collaborate on it.

You state:

I am aware that Firebase is also looking to implement a custom solution to this

Looks like this is what you mean? https://github.com/firebase/firebase-ios-sdk/issues/9906#issuecomment-1159535230

mikehardy avatar Jun 21 '22 18:06 mikehardy

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 avatar Jun 21 '22 18:06 mikehardy

@mikehardy The missing part in that solution is how to get access_token and refresh_token that we need to supply in order to revoke them. Does Firebase store them, can we get them somehow?

I suppose that Firebase at some point calls auth/token ( https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens ) in order to generate those tokens, but maybe I'm wrong.

algrid avatar Jun 21 '22 19:06 algrid

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 https://github.com/invertase/react-native-apple-authentication/blob/9040e0e29508c5e7ed444af3b1aee38217a49811/ios/RNAppleAuthentication/RNAppleAuthASAuthorizationDelegates.m#L43

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 ?

mikehardy avatar Jun 21 '22 19:06 mikehardy

This is a possible work around, if we follow the same steps it may work out: https://stackoverflow.com/a/72498906/321506 Will attempt it on my end and let you guys know!

tmoubarak avatar Jun 21 '22 20:06 tmoubarak

I don't think there needs to be a workaround based on my investigation in comment above. I think the test is:

  • alter this module to, perhaps, store the authorization code in user keychain (so it's available, though it is only valid for 5 minutes per apple docs). Display it in the UI here along with it's expiration so it's visible for testing.
  • make a test button here called "get refresh token", enabled if authorization code has not expired, and implement it in javascript as a POST to the apple REST API using the authorization code and your app secrets as documented. Store the refresh token in keychain so it's available, display the refresh token here in the app if it's available (so now you can see it changing as you request new ones by hitting the button?)
  • make a test button here called "get access token" and implement it here in javascript as a POST to the apple REST API using the refresh token. Store it in keychain for persistent access. Display the access token so you can see it changing

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 avatar Jun 21 '22 21:06 mikehardy

@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 authorizationCode (that we get in the didCompleteWithAuthorization call) at all. Or I simply can't find where it happens. We use identityToken only, passing it over to Firebase.

So can Firebase generate any refresh tokens without authorizationCode? Should we in general revoke any tokens if we don't generate any? Generating tokens only to revoke them later seems weird...

algrid avatar Jun 21 '22 21:06 algrid

btw, storing authorizationCode for long time wouldn't probably make sense:

The code is single-use only and valid for five minutes.

as mentioned here https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens If that's about the token we're talking about.

algrid avatar Jun 21 '22 21:06 algrid

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

mikehardy avatar Jun 21 '22 21:06 mikehardy

Indeed in react-native-firebase we send the identity token in to the OAuth provider along with the nonce but that's it.

https://github.com/invertase/react-native-firebase/blob/c0b5e5c078d82e134c538cbec09d97cc7a35d055/packages/auth/ios/RNFBAuth/RNFBAuthModule.m#L966-L969

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

mikehardy avatar Jun 21 '22 22:06 mikehardy

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

https://github.com/invertase/react-native-apple-authentication/issues/282

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.

mikehardy avatar Jun 22 '22 14:06 mikehardy

Server Side

  • Apple authentication certificate (p8)
  • Extract the PEM key from the p8 certificate (some cases, I saw other solutions directly using the p8 certificate)
  • Client Secret - a JWT token signed with the PEM key (Follow the instructions here)

Also you can refer to this guide I followed the instructions to get my client_secret

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 scene

Apple API Calls

Get the Refresh Token

refers 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 Token

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

  • client secret - call GetAppleAuthClientSecret
  • authorization code - call appleAuth LOGOUT (with this library)

once we have these parameters we can call the AppleAuthRefreshToken query to get our refresh_token and finally call the RequestAppleLoginRevocation mutation using the client_secret and the refresh_token

Also we can use the listener provided by this library onCredentialRevoked to verify that it is working or by going to Settings -> Apple ID -> Password and Security -> Apps using Sign In With Apple

cresenciof avatar Jun 23 '22 15:06 cresenciof

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 avatar Jun 23 '22 16:06 mikehardy

@mikehardy You're right, we can call this REST API's directly from the client. The only thing required is the client_secret, I have read that the client_secret can be generated with an expiration time of up to 6 months. I don't consider generating the JWT from the client, I think it's not possible and we shouldn't expose a private key. So yes, we also need a server side implementation to at least generate the client secret

It gives us two ways to work around it:

  • Get a client secret from a API on each request (Our own server o cloud function)
  • Set a client_secret env with 6 months of validity(involves doing some updates periodically)

cresenciof avatar Jun 23 '22 17:06 cresenciof

Set a client_secret env with 6 months of validity(involves doing some updates periodically)

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

mikehardy avatar Jun 23 '22 17:06 mikehardy

Using AppleAuthRequestOperation.LOGOUT as suggested by @cresenciof looks interesting. Is it better than getting a refresh token right after sign in and storing it?

algrid avatar Jun 23 '22 19:06 algrid

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 avatar Jun 23 '22 19:06 algrid

@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 avatar Jun 23 '22 19:06 mikehardy

@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. From the implementation standpoint I like the idea of not having to store the refresh token, but requiring authentication for a users who's already authenticated looks annoying.

algrid avatar Jun 24 '22 11:06 algrid

@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

mikehardy avatar Jun 24 '22 15:06 mikehardy

Server Side

  • Apple authentication certificate (p8)
  • Extract the PEM key from the p8 certificate (some cases, I saw other solutions directly using the p8 certificate)
  • Client Secret - a JWT token signed with the PEM key (Follow the instructions here)

Also you can refer to this guide I followed the instructions to get my client_secret

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 scene

Apple API Calls

Get the Refresh Token

refers 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 Token

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

  • client secret - call GetAppleAuthClientSecret
  • authorization code - call appleAuth LOGOUT (with this library)

once we have these parameters we can call the AppleAuthRefreshToken query to get our refresh_token and finally call the RequestAppleLoginRevocation mutation using the client_secret and the refresh_token

Also we can use the listener provided by this library onCredentialRevoked to verify that it is working or by going to Settings -> Apple ID -> Password and Security -> Apps using Sign In With Apple

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?

ahmadAlMezaal avatar Jun 27 '22 09:06 ahmadAlMezaal

@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 grant_type, also the Content-Type: application/x-www-form-urlencoded is a very important step to take into account.

cresenciof avatar Jun 27 '22 13:06 cresenciof

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

algrid avatar Jun 27 '22 14:06 algrid

@mikehardy This argument about account deletion being a 'sensitive' operation actually makes sense: https://github.com/firebase/firebase-ios-sdk/issues/9906#issuecomment-1167301269

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

algrid avatar Jun 27 '22 14:06 algrid

@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 grant_type, also the Content-Type: application/x-www-form-urlencoded is a very important step to take into account.

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

const config =
 {
       headers: {
             'Content-Type': 'application/x-www-form-urlencoded'
        }
 };
   

  const authTokenBody = new URLSearchParams(
           {
                  client_id: 'com.example.ex',
                  client_secret: CLIENT_SECRET,
                  code: authorizationCode,
                  grant_type: 'authorization_code'
           }
  );

   const generateAuthTokenUrl = 'https://appleid.apple.com/auth/token';
   const authTokenResponse = await axios.post(generateAuthTokenUrl, authTokenBody, config);


   const revokeAuthTokenBody = new URLSearchParams(
        {
           client_id: 'com.example.ex',
           client_secret: CLIENT_SECRET,
           token: authTokenResponse.data.refresh_token,
           token_type_hint: 'refresh_token'
        }
    );
    
     const revokeAuthTokenUrl = 'https://appleid.apple.com/auth/revoke';
  
     const revokeAuthTokenResponse = await axios.post(revokeAuthTokenUrl, revokeAuthTokenBody, config);

Also found this helpful tutorial from MongoDB to generate the CLIENT_SECRET

Hope it helps anyone struggling with the same

ahmadAlMezaal avatar Jun 28 '22 10:06 ahmadAlMezaal

@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?

ahmadAlMezaal avatar Jun 28 '22 10:06 ahmadAlMezaal

@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 avatar Jun 28 '22 15:06 mikehardy

@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

ahmadAlMezaal avatar Jun 28 '22 16:06 ahmadAlMezaal