Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sign in with apple - authenticate subsequent calls without showing the sign in UI (refreshing authentication token)

Whenever I start my app, I want to make sure apple sign in is still valid, but I also want to be able to prove it to my server.
After a successful login, I can use ASAuthorizationAppleIDProvider.getCredentialState to figure out if my credentials are still valid for subsequent logins.
This works fine, but there is no way to prove to my server that this process was successfully performed, and anyone that uses my server API could just tell the server not to worry and the server would have no way of telling if it's a real client or not.
The server can also use the refresh token to verify the user is still valid, by issuing a new access token (Apple says to do it no more than once a day, but OK for now), but this doesn't help either as I don't want the secret to be present in the client app, and besides, even if I get a new access token from the client side - the server can't use it for anything for the time being as no API currently exists that does anything with the access token.

What I require is a new authentication token, however - when invoking ASAuthorizationController.performRequests() with an Apple ID request - the UI shows again, and does not just calls the ASAuthorizationControllerDelegate with a new authorization token.
I also tried setting the request operation to refresh in the second login attempt:

    let request = appleIDProvider.createRequest()
    request.requestedOperation = .operationRefresh

Same result, the UI still shows.
Is there a way to re-authenticate with Apple ID without showing the UI, and get some sort of token which can be used by the server to verify the authenticity of the login?
Please don't suggest of out of scope methods, such as those that involve my own sign in mechanism after initial proof to the server and having the server use the refresh token from that point on - I can come up with those solutions on my own - I'm looking for an apple sign in approach solution.

like image 901
Moshe Gottlieb Avatar asked Jan 17 '20 09:01

Moshe Gottlieb


People also ask

What is Apple ID authentication token?

Sign in with Apple lets users log in to your app across all of your platforms using their two-factor authentication Apple ID. After the user chooses to use Sign in with Apple to log in, your app receives tokens and user information that you can verify from a server.

Does Sign in with Apple use OAuth?

First, you need to have a client id to represent your application. Apple called this Services ID in the Apple developer portal. Define the name of the app that the user will see during the login flow, as well as define the identifier, which becomes the OAuth client_id . Check the Sign In with Apple checkbox.

How do I authenticate with Apple?

On your iPhone, iPad, or iPod touch: Go to Settings > your name > Password & Security. Tap Turn On Two-Factor Authentication. Then tap Continue and follow the onscreen instructions. On your Mac: Choose Apple menu  > System Settings (or System Preferences), then click your name (or Apple ID).


1 Answers

According to the documentation:

After verifying the identity token, your app is responsible for managing the user session. You may tie the session’s lifetime to successful getCredentialState(forUserID:completion:) calls on Apple devices. This is a local, inexpensive, nonnetwork call and is enabled by the Apple ID system that keeps the Apple ID state on a device in sync with Apple servers.

User interaction is required any time a new identity token is requested. User sessions are long-lived on device, so calling for a new identity token on every launch, or more frequently than once a day, can result in your request failing due to throttling.

If the user’s Apple ID changes in the system, calls to getCredentialState(forUserID:completion:) indicate that the user changed. Assume that a different user has signed in and log out the app’s currently known user.

For apps running on other systems, use the periodic successful verification of the refresh token to determine the lifetime of the user session.

So this basically means you should be checking the userIdentifier when getting result from getCredentialState and make sure it is the same value as the current user has. And you can't avoid the user interaction you mention in the question, if you want to re-authenticate in the app.

First of all, you have to take the userIdentifier and the authorizationCode from the app sign in and send it to your server. Also make a long session id and send that to your server too:

if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {

        let userIdentifier = appleIDCredential.user
        print("userIdentifier: \(userIdentifier)")

        let authorizationCode = String(data: appleIDCredential.authorizationCode!, encoding: .utf8)!
        print("authorizationCode: \(authorizationCode)")  

        let sessionID = veryLongToken

        //send authorizationCode, userIdentifier and sessionId to server
    }

On your server you obtain the identityToken when calling: appleid.apple.com/token/auth

with:

#create token
import jwt  # pip install pyjwt
claims = {
    'iss': teamID,
    'iat': timeNow,
    'exp': time3Months,
    'aud': 'https://appleid.apple.com',
    'sub': app_id,
}
client_secret = jwt.encode(claims, private_key_from_developer_portal, algorithm='ES256', headers={'kid': kid})


data = {
    'client_id': app_id,
    'client_secret': client_secret,
    'grant_type': grant_type,
    'redirect_uri': redirect_uri,
    'code': authorizationCode,
}

headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
}

response = requests.request(
    method='POST',
    url='https://appleid.apple.com/auth/token',
    data=data,
    headers=headers,
)
#response will include refresh_token, access_token and id_token

You can now retrieve the user email and the userIdentifier from the id_token:

result = jwt.decode(id_token, verify=False)
print("jwtResult: " + result)

userIdentifier = result["sub"]
email = result["email"]

Now you know from apple what user it is. According to validate token documentation you first use authorization_code in grant_type to get the refresh_token and after that use refresh_token as grant_type

This is the only way to verify the user is still authenticated on the server.

So store the sessionId along with some information about the apple sign in (refresh token...) on your server.

You can make the app send the sessionId to the server when the app needs data from the server and before handing over some information to the app, do the refresh token validation on the server. If the authentication is not valid, then the app should delete the sessionId and log out.

This is the only way you are going to be able to validate on your server if the authentication is valid.

By reading the documentation this seems to be the recommended way and what you are trying to do is going to cause problems and apple doesn't want you to authenticate the user in the app all the time.

You should also check out this example project from apple for sign in with apple.

juice example project

like image 87
DevB2F Avatar answered Nov 15 '22 11:11

DevB2F