read

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. ABCDE12345
  • iss: The 10-char Team ID eg. 67890ABCDE
  • sub: The client_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.


Image

@samwize

¯\_(ツ)_/¯

Back to Home