This is a developer guide on integrating Apple as an OAuth provider on Apple platform, as well as for other platforms such as Android/Web.
The amount of Swift code is actually not much, as you can see in the last section. The more confusing aspect is with the configurations, especially the client_id.
Config App ID
On Apple Developer portal, edit the app ID and enable Sign In with Apple.
Config Service ID
If you’re supporting log in on platforms other than Apple, then you need to also add a service ID on the developer portal.
Usually, you will append a “service” to the app bundle ID. eg. com.just2us.great.service
You will also configure the associated primary app and the website URLs for OAuth.
Config Sign In Key
In the developer portal, go to Keys > add a new Sign in with Apple.
Download the p8 private key. This is needed, along with the Key ID, to generate the client_secret
(in later section).
What is client_id?
With the above setup, let’s clarify the most confusing aspect of Apple OAuth – what is the client_id
?
In your backend, you will need to validate grant code (passed from app), and there are a few parameters that you need to send to Apple.
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' \
-d 'redirect_uri=REDIRECT_URI'
The very first parameter is the client_id
, which as documented:
(Required) The identifier (App ID or Services ID) for your app. The identifier must not include your Team ID, to help prevent the possibility of exposing sensitive data to the end user.
Does it mean you can pass either App ID or Service ID? Nope.
You need to pass the App ID when the grant code is from an Apple app.
For Android and Web, it is the Service ID.
If not, the backend will encounter error:
{“error”:”invalid_client”}
What is the client_secret?
Another parameter that takes some effort to produce is the client secret. As per Apple doc, it is:
(Required) A secret JSON Web Token, generated by the developer, that uses the Sign in with Apple private key associated with your developer account. Authorization code and refresh token validation requests require this parameter.
The client_secret
is a JWT (JSON Web Token) which the backend has to generate using Apple Sign In private key (in earlier section). How you generate the JWT depends on your backend, but as JWT is an open industry standard, you can find compatible libraries easily.
Read about in on Apple doc, or this post which uses ruby-jwt.
Nonetheless, there are 3 keys which can be confusing, and which you must get right:
kid
: The 10-char Sign In Key eg. ABCDE12345iss
: The 10-char Team ID eg. 67890ABCDEsub
: Theclient_id
(read above section)
Plus, you need to use the p8 private key earlier. With all that, you can generate a JWT.
What is the code
This is a grant code that is returned to your app after a user has successfully signed in. The app will usually send the grant code to the backend to validate.
Let’s move on to the app integration and see how we can get the code.
Integrating on iOS
Assuming you have a button to trigger the sign in with Apple, this is the on tapped func to kickstart the sign in:
@objc func appleLoginTapped() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
Your view controller then has to implement the 2 protocols:
extension LoginViewController: ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return view.window!
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
guard
let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential,
let code = appleIDCredential.authorizationCode, let codeString = String(data: code, encoding: .utf8)
else { return }
// Send codeString to backend
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
if let authError = error as? ASAuthorizationError, authError.code != .canceled {
// Handle error
}
}
}
If the user has logged in successfully, you will have the grant code codeString
which you can then send to your backend.