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.
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.
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.
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).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With