How to Make Apple Sign in Revoke Token Post Request

Revoke Apple sign in token for account deletion process

After doing a bit of research and spending a few hours, understand the flow.

Just a note that in my case I’ve implemented the apple sign-in option in the native iOS app.

Here are the three important steps that need to be followed to revoke the token.

  1. Get authorizationCode from Apple login (client side).
  2. Get a refresh token or access token with no expiry time using authorizationCode through auth\token (server side).
  3. Revoke the refresh token or access token through token\revoke (server side).

Client Side(App side):

  1. Get authorizationCode from Apple login.
  • After sucessfully login in app you will get authorization code from the apple native didCompleteWithAuthorization delegate call.

  • When you receive the authorization code you will need to send the code to the server immediately, as the code is one use only and valid for five minutes.

     func authorizationController(controller: ASAuthorizationController,
    didCompleteWithAuthorization authorization: ASAuthorization) {

    if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
    let authorizationCode = String(data: appleIDCredential.authorizationCode!, encoding: .utf8)!
    }
    }

Server side (backend side):


  1. Get a refresh token or access token with no expiry time using
  • Once received authorization code from the client side, You will need to validate this code via auth\token.

  • When you send an authorization request to the validation server(Apple server), include the following form data parameters.

  • client_id = "com.demo.app" (your app bundle id)

  • client_secret = A secret JSON Web Token, generated by the developer, that uses the Sign in with Apple private key associated with your developer account.

  • code = The authorization code received in an authorization response sent to your app

  • Important: Create the client secret (client_secret) GET REFERENCE FROM THE APPLE DEVELOPER DOCUMENTATION.

    • JSON Web Token (JWT) is an open-standard (RFC 7519) that defines a way to transmit information securely. Sign in with Apple requires JWTs to authorize each validation request. Create the token, then sign it with the private key you downloaded from Apple Developer.

    • To generate a signed JWT:


        1. Create the JWT header.


        1. Create the JWT payload.


        1. Sign the JWT.
    • To create a JWT, use the following fields and values in the JWT header:

    alg --> The algorithm used to sign the token. For Sign in with Apple, use ES256.

    kid --> A 10-character key identifier generated for the Sign in with Apple private key associated with your developer account.

    • The JWT payload contains information specific to the Sign in with Apple REST API and the client app, such as issuer, subject, and expiration time. Use the following claims in the payload:

    is --> Use your 10-character Team ID associated with your developer account.

    iat --> The issued at registered claim indicates the time at which you generated the client secret, in terms of the number of seconds since Epoch, in UTC.

    exp --> The expiration time registered claim identifies the time on or after which the client secret expires. The value must not be greater than 15777000 (6 months in seconds) from the Current UNIX Time on the server.

    aud --> https://appleid.apple.com.

    sub --> Use the same value as client_id. The value is case-sensitive.(app bundle id).

  • After creating the JWT, sign it using the Elliptic Curve Digital Signature Algorithm (ECDSA) with the P-256 curve and the SHA-256 hash algorithm. A decoded client_secret JWT token has the following format:

     {
    "alg": "ES256",
    "kid": "AEBD123DEPG"
    }

    {
    "iss": "EED153GJIJ",
    "iat": 1437179036,
    "exp": 1493298100,
    "aud": "https://appleid.apple.com",
    "sub": "com.demo.app"
    }
  • After the server validates the refresh token, the endpoint returns the identity token and an access token. The following is an example refresh token validation response:

     {
    "access_token": "beg3456...67Or9",
    "token_type": "Bearer",
    "expires_in": 3600,
    "id_token": "eyPgkk...96sZg"
    }

Revoke the refresh token or access token through token\revoke (server side).

  • In order to revoke authorization for a user, you must obtain a valid refresh token or access token that you get in step (2).

  • Once you have a valid refresh or access_token you will be able to revoke the token via token\revoke end point.

  • There are below parameters required for the server to invalidate the token.

  • client_id = "com.demo.app" (your app bundle id)

  • client_secret = "A secret JSON Web Token same way you generate in the step 2".

  • token = access_token which is what you get from the step 2 end point call.

Once the access token revokes client side gets a notification for the same, for that client needs to add the below the observer.

func addObserverforRevokeAppleSignToken() {
let sessionNotificationName = ASAuthorizationAppleIDProvider.credentialRevokedNotification
NotificationCenter.default.addObserver(forName: sessionNotificationName, object: nil, queue: nil) { (notification: Notification) in
// Sign user out
print("Apple sign in token revoked....")
}
}

You can check Settings - Password & Security > Apps Using Apple ID.

Thank you.

Where I get from IOS Firebase API the Apple Revoke Tokens Endpoint Parameters (client_id, client_secret, token)


apple-token-revoke-in-firebase

This document describes how to revoke the token of Sign in with Apple in the Firebase environment.

In accordance with Apple's review guidelines, apps that do not take action by June 30, 2022 may be removed.

A translator was used to write this document, so I apologize whenever you feel weird about these sentences and describes.

This document uses Firebase's Functions, and if Firebase provides related function in the future, I recommend using it.

The whole process is as follows.

  1. Get authorizationCode from App where user log in.
  2. Get a refresh token with no expiry time using authorizationCode with expiry time.
  3. After saving the refresh token, revoke it when the user leaves the service.

You can get a refresh token at https://appleid.apple.com/auth/token and revoke at https://appleid.apple.com/auth/revoke.

Getting started

If you have implemented Apple Login using Firebase, you should have ASAuthorizationAppleIDCredential somewhere in your project.

In my case, it is written in the form below.

  func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
guard let nonce = currentNonce else {
fatalError("Invalid state: A login callback was received, but no login request was sent.")
}
guard let appleIDToken = appleIDCredential.identityToken else {
print("Unable to fetch identity token")
return
}
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
return
}
// Initialize a Firebase credential.
let credential = OAuthProvider.credential(withProviderID: "apple.com",
IDToken: idTokenString,
rawNonce: nonce)
// Sign in with Firebase.
Auth.auth().signIn(with: credential) { (authResult, error) in
if error {
// Error. If error.code == .MissingOrInvalidNonce, make sure
// you're sending the SHA256-hashed nonce as a hex string with
// your request to Apple.
print(error.localizedDescription)
return
}
// User is signed in to Firebase with Apple.
// ...
}
}
}

What we need is the authorizationCode. Add the following code under guard where you get the idTokenString.

...

guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
return
}

// Add new code below
if let authorizationCode = appleIDCredential.authorizationCode,
let codeString = String(data: authorizationCode, encoding: .utf8) {
print(codeString)
}

...

Once you get this far, you can get the authorizationCode when the user log in.

However, we need to get a refresh token through authorizationCode, and this operation requires JWT, so let's do this with Firebase functions.
Turn off Xcode for a while and go to your code in Firebase functions.

If you have never used functions, please refer to https://firebase.google.com/docs/functions.

In Firebase functions, you can use JavaScript or TypeScript, for me, I used JavaScript.

First, let's declare a function that creates a JWT globally. Install the required packages with npm install.

There is a place to write route of your key file and ID(Team, Client, Key), so plz write your own information.

If you do not know your ID information, please refer to the relevant issue. https://github.com/jooyoungho/apple-token-revoke-in-firebase/issues/1

function makeJWT() {

const jwt = require('jsonwebtoken')
const fs = require('fs')

// Path to download key file from developer.apple.com/account/resources/authkeys/list
let privateKey = fs.readFileSync('AuthKey_XXXXXXXXXX.p8');

//Sign with your team ID and key ID information.
let token = jwt.sign({
iss: 'YOUR TEAM ID',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 120,
aud: 'https://appleid.apple.com',
sub: 'YOUR CLIENT ID'

}, privateKey, {
algorithm: 'ES256',
header: {
alg: 'ES256',
kid: 'YOUR KEY ID',
} });

return token;
}

The above function is returned by creating JWT based on your key information.

Now, let's get the Refresh token with AuthorizationCode.

We will add a function called getRefreshToken to functions.

exports.getRefreshToken = functions.https.onRequest(async (request, response) => {

//import the module to use
const axios = require('axios');
const qs = require('qs')

const code = request.query.code;
const client_secret = makeJWT();

let data = {
'code': code,
'client_id': 'YOUR CLIENT ID',
'client_secret': client_secret,
'grant_type': 'authorization_code'
}

return axios.post(`https://appleid.apple.com/auth/token`, qs.stringify(data), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
})
.then(async res => {
const refresh_token = res.data.refresh_token;
response.send(refresh_token);

});

});

When you call the above function, you get the code from the query and get a refresh_token.
For code, this is the authorizationCode we got from the app in the first place.
Before connecting to the app, let's add a revoke function as well.


exports.revokeToken = functions.https.onRequest( async (request, response) => {

//import the module to use
const axios = require('axios');
const qs = require('qs');

const refresh_token = request.query.refresh_token;
const client_secret = makeJWT();

let data = {
'token': refresh_token,
'client_id': 'YOUR CLIENT ID',
'client_secret': client_secret,
'token_type_hint': 'refresh_token'
};

return axios.post(`https://appleid.apple.com/auth/revoke`, qs.stringify(data), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
})
.then(async res => {
console.log(res.data);
});
});

The above function revokes the login information based on the refresh_token we got.

So far we have configured our functions, and when we do 'firebase deploy functions' we will have something we added to the Firebase functions console.

img

Now back to Xcode.

Call the Functions address in the code you wrote earlier to save Refresh token.

I saved it in UserDefaults, You can save it in the Firebase database.

...

// Add new code below
if let authorizationCode = appleIDCredential.authorizationCode, let codeString = String(data: authorizationCode, encoding: .utf8) {

let url = URL(string: "https://YOUR-URL.cloudfunctions.net/getRefreshToken?code=\(codeString)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "https://apple.com")!

let task = URLSession.shared.dataTask(with: url) {(data, response, error) in

if let data = data {
let refreshToken = String(data: data, encoding: .utf8) ?? ""
print(refreshToken)
UserDefaults.standard.set(refreshToken, forKey: "refreshToken")
UserDefaults.standard.synchronize()
}
}
task.resume()

}

...

At this point, the user's device will save the refresh_token as UserDefaults when logging in.
Now all that's left is to revoke when the user leaves the service.

  func removeAccount() {
let token = UserDefaults.standard.string(forKey: "refreshToken")

if let token = token {

let url = URL(string: "https://YOUR-URL.cloudfunctions.net/revokeToken?refresh_token=\(token)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "https://apple.com")!

let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard data != nil else { return }
}

task.resume()

}
...
//Delete other information from the database...
FirebaseAuthentication.shared.signOut()
}

If we've followed everything up to this point, our app should have been removed from your Settings - Password & Security > Apps Using Apple ID.

Thank you.

How to revoke Sign in with Apple credentials for a specific app?

You can do this from the iPhone Settings.
Open the Settings app then tap on your name at the top. Then press "Password & Security", then "Apple ID logins".
They should all be listed there and can be deleted.

How do I do sign in with apple ID with json and revoke token when user wants to permanently deactivate account in flutter/dart?

This signin with apple is only an authentication method. Once a user authenticates using this method you will get a unique email or id of that user. Then with this email you will be saving the user in your db or firebase. Apple wants us to create a mechanism where the users can permanently delete their account. In this case you should be deleting this database entry permanently and logging out the user. They also approve manual deletion process where a person will manually delete the account from the db but we should provide an option for the user to initiate this processs from the app. And it should be clearly visible saying delete account maybe.



Related Topics



Leave a reply



Submit