3

Per Account deletion requirement iOS

If your app offers Sign in with Apple, you’ll need to use the Sign in with Apple REST API to revoke user tokens when deleting an account.

Referring to this answer, we are trying to send this revoke token API on our server-side. Here are some snippet

        privateKey = fs.readFileSync("xxxxxxxx.p8")
        client_secret = jwt.sign({ 
                iss: 'xxxx-xxx-xx-xxxx-xxxxxxxx',
                iat: Math.floor(Date.now() / 1000),
                exp: Math.floor(Date.now() / 1000) + 1200,
                aud: 'https://appleid.apple.com',
                sub: "sample.com"
            }, 
            privateKey, 
            { 
                algorithm: 'ES256',
                header: {
                    alg: 'ES256',
                    kid: 'xxxxxxxxxxx'
                } 
            });

        data = {
            'token': token,
            'client_id': "sample.com",
            'client_secret': client_secret
        };
        body = qs.stringify(data)

        opts =
            protocol: 'https:'
            host: 'appleid.apple.com'
            path: '/auth/revoke'
            method: 'POST'
            timeout: 6000
            headers:
                'Content-Type': 'application/x-www-form-urlencoded'
                'Content-Length': Buffer.byteLength(body)
       // call https to send this opts message

And the status code of the above codes could be 200 and the response body is empty.

However, the response code 200 of revoke token api

The request was successful; the provided token has been revoked successfully or was previously invalid.

It seems the status code 200 includes the provided token was previously invalid. How could we distinguish whether the revoke token API was returned by the invalid token or revoked successfully?

We also try to test this revoke token API through curl with invalid client_secret and token, the status code 200 could be returned either and the response body is empty. It is so weird.

curl -v POST "https://appleid.apple.com/auth/revoke" \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'client_id=xxx.xxxx.yyyy' \
-d 'client_secret=ddddddeyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlBGUVRYTTVWUlcifQ.dddd.DmMifw6qWHMqKgDIbO8KrIzDvbF7T4WxxEo9TmtN0kmTISsi8D8FG52k_LPGkbNEnS_-w_SRimEKIH1rsuawFA' \
-d 'token=dddddd' \
-d 'token_type_hint=access_token'

> POST /auth/revoke HTTP/1.1
> Host: appleid.apple.com
> User-Agent: curl/7.77.0
> Accept: */*
> content-type: application/x-www-form-urlencoded
> Content-Length: 240
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Server: Apple
< Date: Thu, 09 Jun 2022 07:36:31 GMT
< Content-Length: 0
< Connection: keep-alive
< Host: appleid.apple.com
zangw
  • 43,869
  • 19
  • 177
  • 214

1 Answers1

4

Finally, we could call revoke token api (appleid.apple.com/auth/revoke) successfully, and the apple id binding information is deleted under Apps Using Apple ID of Settings


The root cause is that an invalid token has been used before. We try the identity token of the apple signing result, it is not the correct token.

The correct token is access_token or refresh_token returned from auth/token.

code - The authorization code received in an authorization response sent to your app. The code is single-use only and valid for five minutes. Authorization code validation requests require this parameter.

In order to get access_token or refresh_token through auth/token, the code parameter of auth/token request should be paid attention. The code authorization response of apple signing, and its type is base64. It should be decoded to utf-8 before assigning to auth/token API.


Summary the whole process as below.

  • Get authorizationCode from Apple login.
  • Get a refresh token \ access token with no expiry time using authorizationCode through auth\token
  • Revoke the refresh token or access token through token\revoke

Hope the above could help someone who meet the same issue. Here are node.js code snippets.

    getClientSecret: () ->
        client_secret = jwt.sign({ 
                            iss: 'xxxxxxxxx',
                            iat: Math.floor(Date.now() / 1000),
                            exp: Math.floor(Date.now() / 1000) + 360000,
                            aud: 'https://appleid.apple.com',
                            sub: bundleID
                        }, 
                        @privateKey, 
                        { 
                            algorithm: 'ES256',
                            header: {
                                alg: 'ES256',
                                kid: 'xxxxxxxxxx'
                            } 
                        });
        client_secret

    decodeBase64: (base64Data) ->
        buff = Buffer.from(base64Data, 'base64')
        return buff.toString('utf-8')

    revokeToken: (token) ->
        client_secret = @getClientSecret()

        data = {
            'token': token,
            'client_id': bundleID,
            'client_secret': client_secret,
            'token_type_hint': 'access_token'
        };

        body = qs.stringify(data)

        opts =
            protocol: 'https:'
            host: 'appleid.apple.com'
            path: '/auth/revoke'
            method: 'POST'
            timeout: 6000
            headers:
                'Content-Type': 'application/x-www-form-urlencoded'
                'Content-Length': Buffer.byteLength(body)

        http.post(body, opts)

    authToken: (authCode) ->
        client_secret = @getClientSecret()
        code = @decodeBase64(authCode)

        data = {
            'code': code,
            'client_id': bundleID,
            'client_secret': client_secret,
            'grant_type': 'authorization_code'
        };

        body = qs.stringify(data)

        opts =
            protocol: 'https:'
            host: 'appleid.apple.com'
            path: '/auth/token'
            method: 'POST'
            timeout: 6000
            headers:
                'Content-Type': 'application/x-www-form-urlencoded'
                'Content-Length': Buffer.byteLength(body)

        http.post(body, opts)
zangw
  • 43,869
  • 19
  • 177
  • 214
  • Do you know by any chance how to get `access_token` and `refresh_token` when using Firebase Authentication? – algrid Jun 20 '22 at 16:35
  • @algrid, It seems this feature is working on Firebase per https://github.com/firebase/firebase-ios-sdk/issues/9906#issuecomment-1159535230 – zangw Jun 21 '22 at 02:48
  • yes, I saw that, not sure how long it will take for them to implement it though. I want to implement it myself for now, but I can't find a way to get those tokens. – algrid Jun 21 '22 at 11:56
  • About how to get this token, please refer to https://stackoverflow.com/a/72656672/3011380, hope it could help you @algrid – zangw Jun 21 '22 at 12:24