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:
- scope
- I used “openid email name,” and it seemed to work even though I never got back any of those requested details. According to a forum entry, those will be supported soon.
- The string must be encoded as:
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”)
- response_mode=form_post
- Your request fails if you are requesting more than “scope=openid” and not using response_mode=form_post.
- Be prepared to handle responses to your redirect_uri via POST!
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.
- Method: POST
- URL: https://appleid.apple.com/auth/token
- Header: Content-Type: application/x-www-form-urlencoded
- Body:
client_id={client_id}
&client_secret={client_secret}
&grant_type=authorization_code
&code={code}
&redirect_uri={redirect_uri}
- client_id
- This is the service identifier that you created in the developer console. It’s a value that includes your Team_ID.
- client_secret
- This is the biggest difference to other solutions. You have to generate the secret yourself based on your application’s private key which was generated during the configuration process in the developer console!
- The generated string has to be a signed JWT using the ES256 algorithm
- You set the expiration date in seconds (up to six months)
- The complete content of the JWT is explained here
- For your convenience, we’ve built a simple to use Java tool here: Client Secret Generator
- Redirect_uri
- Nothing different here, just make sure it is the same value as in your initial authorization request
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.