a good thing!
Sign In with Apple
I recently took on the task of implementing Apple’s new “Sign In with Apple” feature. The implementation is based on OAuth and OpenID Connect, which allowed me to use tools that I already had.
But some other aspects of the implementation weren’t as straightforward. I learned a few lessons along the way that I think will be helpful—and aren’t obvious when reading through their documentation—so I’m sharing them here.
Please note the information that follows applies only to web applications using the OAuth authorization_code flow via REST API, not the JavaScript version.
As mentioned previously, the Apple implementation is a bit more involved than other providers like Google. Because of Apple’s emphasis on keeping their users’ privacy truly private (which I am a big supporter of), before you implement Sign in with Apple, you must meet a couple requirements first and be aware of some additional nuances.
Prerequisites:
You do this with all providers, but in this case it is not free of charge
In the process of configuring your application, you need to be able to prove that you are in control of the domain for which you want to leverage the feature. If you do not have this proof, you won’t get ahead. Also note that only https is supported.
Things To Be Aware Of:
Apple’s documentation is a little light on this, but you must have support for SPF mailing addresses. The intent is to prevent anyone besides yourself from sending emails to the users signed in to your application. To support SPF, you need to add a TXT record to your DNS server.
Thus far in my development, I haven’t been successful in gathering a name or an email address of a user that has signed in. The only value I was able to retrieve was the sub value (subject). I also didn’t find a list of documented OAuth/OpenID Connect scope values, and a /userinfo endpoint does not exist either. I did find something interesting, though, that I share further down.
Now that you’re aware of what’s coming, you can create the developer account and start developing the support for Sign In with Apple.
Since I started from scratch, with no previous development experience in the Apple universe, I went through the complete setup including the generation of a developer certificate. Here is a comprehensive list of tasks as documented by Apple.
Start by locating the Certificates, Identifiers & Profiles menu in the developer console and following the steps provided here.
Certificates
Identifiers
Keys
More
MIIP1wYJKoZIhvcNAQcCoIIPyDCCD8QCAQExCzAJBgUrDgMCGgUAMGwGCSqGSIb3DQEHAaBfBF17
InRlYW1JZCI6IkZaUE5VS1JORkIiLCJkb21haW4iOiJvYXV0aC5ibG9nIiwiZGF0ZUNyZWF0ZWQi
...
...
WBTHhIlGqFkw03X_AcdUUdTvL-vYyc_yHSocwOFAYzFi8Ds_NNBoxbZ5mmWxQbNAKIOSo30q_0V8
msBMLV7lR3_0E3zl
Congratulations! If you’ve mastered this part of the journey, the implementation itself can begin.
If you are experienced with implementing the OAuth authorization_code flow—and handling OpenID Connect scenarios—this implementation is similar. Nevertheless, I’ll share what worked for me.
NOTE: All values in {...} brackets have to be URLEncoded. This is a common oversight, so ensuring this will save you some time and frustration.
Authorization Request
Here is the request I created:
GET https://appleid.apple.com/auth/authorize?
client_id={client_id}
&response_type=code
&redirect_uri={redirect_uri}
&scope={scope}
&response_mode=form_post
&state={state}
Looks familiar, yes? BUT there are two hidden details:
openid%20email%20name
instead of
openid+email+name
This last detail is IMPORTANT. If you are using java URLEncoder.encode(scope, “UTF-8”) you will end up with the latter. Use a library that supports %20 or do this as a short-term fix: URLEncoder.encode(scope, “UTF-8”).replaceAll(“[+]”, “%20”)
In my request, I also used PKCE where the request includes these additional parameters:
code_challenge={code_challenge}&code_challenge_method=S256
This does not harm the flow. To be honest, though, I have no idea if PKCE is actually supported or not.
After the user is taken through the authentication and authorization process, the response will include the given state and the issued code, just as usual:
{redirect_uri}?state={state}&code={code}
Token Exchange Request
After you receive the authorization_code, you need to exchange the code for an access_token. The token response will include an id_token as well. The access_token currently has no use but the id_token does. All user information will be found within that token.
client_id={client_id}
&client_secret={client_secret}
&grant_type=authorization_code
&code={code}
&redirect_uri={redirect_uri}
After receiving the token response you have to validate the id_token. Luckily, Apple supports JWKS. This is the endpoint:
GET https://appleid.apple.com/auth/keys
Do yourself a favor and do not implement the JWT validation process yourself! Use existing libraries such as jose4j following this example.
You now know everything that I know about Sign In with Apple. If you felt this was too much, here are the highlights to remember:
Thanks for reading and please leave constructive criticism, enhancement requests or joyful shout-outs in the comments!
Related Links
Apple Developer Site: https://developer.apple.com
Apple instructions: https://help.apple.com/developer-account/?lang=en#/devde676e696
Using the REST API: https://developer.apple.com/documentation/signinwithapplerestapi
Client Secret Generator tool:
https://github.com/pingidentity/pingone-customers-sample-scripts
SPF testing tool:
https://www.kitterman.com/spf/validate.html
Digitalocean on SPF:
https://www.digitalocean.com/community/tutorials/how-to-create-a-spf-record-for-your-domain-with-google-apps
Setting up SPF: https://blog.returnpath.com/protecting-your-brand-from-phishing-how-to-create-your-spf-record/