Apple and OIDC Login
oidcapple August 11, 2020Lessons learned when configuring Apple for OIDC Login
First at all, it’s hard to find proper documentation on how to configure Apple. And most probably, these instructions might change over time.
These are the most significant references that we found:
- https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple
- https://bitbucket.org/openid/connect/src/master/How-Sign-in-with-Apple-differs-from-OpenID-Connect.md
- https://appleid.apple.com/.well-known/openid-configuration
- https://stackoverflow.com/questions/18971983/curl-requires-curlopt-ssl-verifypeer-false
- https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens
How to configure Apple as an OP, create a simple RP client and a local server to handle the redirect
On Apple
You’ll need an Apple Developers account, if you don’t have one, well, it costs 100 USD per year :angry:
In general, you’ll need to create an App, Service (which is the client_id), and a private key. The private key is downloaded, and using a ruby script, you can generate the client_secret (which is a jwt token). The steps are detailed here: https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple
Client RP
login.php
<?php
session_start();
$client_id = 'com.oidcserviceid';
$redirect_uri = 'http://eitan.be.com/demo/apple/redirect.php';
$_SESSION['state'] = bin2hex(random_bytes(5));
$authorize_url = 'https://appleid.apple.com/auth/authorize'.'?'.http_build_query([
'response_type' => 'code',
'response_mode' => 'form_post',
'client_id' => $client_id,
'redirect_uri' => $redirect_uri,
'state' => $_SESSION['state'],
'scope' => 'openid email name', //name email
]);
echo '<a href="'.$authorize_url.'">Sign In with Apple</a>';
?>
Server side
Note that you’ll need a PHP server running the redirect page The built-in PHP server can be used (php -S 127.0.0.1:8080) Although, on Windows, I prefer to install PHP over IIS and have a dedicated site
redirect.php
<?php
$client_id = 'com.oidcserviceid';
$client_secret = 'CLIENT-SECRET_GOES-HERE';
$redirect_uri = 'http://eitan.be.com/demo/apple/redirect.php';
if(isset($_POST['code'])) {
#if($_SESSION['state'] != $_POST['state']) {
#die('<p>Authorization server returned an invalid state parameter</p>');
#}
// Token endpoint docs:
// https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens
$response = http('https://appleid.apple.com/auth/token', [
'grant_type' => 'authorization_code',
'code' => $_POST['code'],
'redirect_uri' => $redirect_uri,
'client_id' => $client_id,
'client_secret' => $client_secret,
]);
echo '<h3>Access Token Response</h3>';
echo '<pre>'; print_r($response); echo '</pre>';
if(!isset($response->access_token)) {
echo '<p>Error getting an access token:</p>';
echo '<p><a href="../">Start Over</a></p>';
die();
}
$claims = explode('.', $response->id_token)[1];
$claims = json_decode(base64_decode($claims));
echo '<h3>Parsed ID Token</h3>';
echo '<pre>';
print_r($claims);
echo '</pre>';
die();
}
function http($url, $params=false) {
echo $url; echo "?"; echo(http_build_query($params));
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if($params)
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: */*',
'Connection: keep-alive',
'Accept-Encoding: br,gzip,deflate',
'User-Agent: eitan.be.com' # Apple requires a user agent header at the token endpoint
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response);
}
?>
Additional Info
- The user name is supplied only during the first connection, upon accepting the consent screen
- The user email is supplied on every authorization request, if the user selected sharing the email address
First connection sample
Authorization response (code) * note that it includes the user’s firstName and lastName attributes *
{ ["state"]=> string(10) "4c787429d2" ["code"]=> string(63) "c37e98af176a14ca1949c2482c646454c.0.nwqv.rLqViV0PdM01Z1cTXMNuRA" ["user"]=> string(77) "{"name":{"firstName":"Eitan","lastName":"Behar"},"email":"eitan@baconao.net"}" }
Token request response (token)
{"access_token":"ac9ab2c17c9224c0fb3fba2ce380f714c.0.mwqv.8dcUtL9a3zDiMjNsG3V4uA","token_type":"Bearer","expires_in":3600,"refresh_token":"r97d86e2cb7b5492ca2a806c8d59b0209.0.mwqv.5fkAUqUDgdvh8RzsMqmzGw","id_token":"eyJraWQiOiI4NkQ4OEtmIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdpZ3lhLm9pZGNzZXJ2aWNlaWQiLCJleHAiOjE1OTcxMzAzNDYsImlhdCI6MTU5NzEyOTc0Niwic3ViIjoiMDAwNjA1LjYwMmFjZGQ2ODZjZjQwNzU5OGQ1Mjc3YWE3ZmE3Nzc5LjA5MzQiLCJhdF9oYXNoIjoid0l3eE9YOUdpRG9MTGNOZzhSMk1tUSIsImVtYWlsIjoiZWl0YW5AYmFjb25hby5uZXQiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJhdXRoX3RpbWUiOjE1OTcxMjk3NDQsIm5vbmNlX3N1cHBvcnRlZCI6dHJ1ZX0.CYSWrBgvfhWQXC9I3W0FIY2C7DMjbjesH0srEDssVdOF-rnjgRc23ReJ11s-Yz_CqCVsBAUkAhvyGHVUrnUC0Xq8jxtLnHY9-HUyvS2xxLGUQJ31cBsMZH87d4gWLZZY4NKqV-qB-sdMsXqmgEwH3cNXLHDHTFFZ7wrSHvGE-YXGec0Bv9jdRSKgG1Wre5JnRMCW3uze7aCqUczoS7vZaefzWT6fdOyOFvyklzc5RvgkrZljEva5K7Hg6riWSSlT2qcSwCCV1PivrVPE6mh18xso7jLPeHKO9jDLEtvUY1wB1pJox7314wb1DCrfmCjFGwbDOsIhQa40v_LIyvJgQg"}
Parsed ID Token
stdClass Object
(
[iss] => https://appleid.apple.com
[aud] => com.oidcserviceid
[exp] => 1597130346
[iat] => 1597129746
[sub] => 000605.602acdd686cf407598d5277aa7fa7779.0934
[at_hash] => wIwxOX9GiDoLLcNg8R2MmQ
[email] => eitan@baconao.net
[email_verified] => true
[auth_time] => 1597129744
[nonce_supported] => 1
)
Subsequent connection sample
Authorization response (code) * note that it does not include the user’s firstName and lastName attributes *
{ ["state"]=> string(10) "c86fc76ca6" ["code"]=> string(63) "c4ffd4cb5042d4b23ac7eff816a88e4d2.0.nwqv.4X5XxRlTkNzsQP6fbObp4A" }
Token request response (token)
{"access_token":"ab426e0243aa54d3ca5728154e5aa8b9e.0.mwqv.E0AQFfOR32cobupA_GXvbw","token_type":"Bearer","expires_in":3600,"refresh_token":"raf27d36dabea494d8c0a4db138805aa4.0.mwqv.3gpBlMnpWB2uTBnq78enAQ","id_token":"eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdpZ3lhLm9pZGNzZXJ2aWNlaWQiLCJleHAiOjE1OTcxMzA0MjIsImlhdCI6MTU5NzEyOTgyMiwic3ViIjoiMDAwNjA1LjYwMmFjZGQ2ODZjZjQwNzU5OGQ1Mjc3YWE3ZmE3Nzc5LjA5MzQiLCJhdF9oYXNoIjoiOU91UzduTHFMRFdmRnRmZVA0aVRwZyIsImVtYWlsIjoiZWl0YW5AYmFjb25hby5uZXQiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJhdXRoX3RpbWUiOjE1OTcxMjk4MjEsIm5vbmNlX3N1cHBvcnRlZCI6dHJ1ZX0.0sD0vyQHC4-WCvRrMjckcSuaeid7fNoF_oaSPIII8HwAINiOZKDDaiA-W23Ci2FXM7M0j41IvL9ltmmAIMuJ34CWe8p4r2gNHHLf9xJVQkP_IzNSVidOVECNclQRy2D0ajMYSzAch0Q3FKCiaZOLZ5D2OC3MXvOR4Rb-VOvK-7Je5tF28dPNC7zdYhCN7QjfmUZ6agpk51ST1htYMROPp-CcXj6pIQENT2ssrBFWLtx-jscAKR2zH1qbzVW-d4HBTnpaoJ2VujOW71UyGgA9txhl72-xKL6axId-6gYjmgxu554ji0VLEeF8vVJ0-M1ROxb138Ht_LzT-YgRLv4kPw"}
Parsed ID Token
stdClass Object
(
[iss] => https://appleid.apple.com
[aud] => com.oidcserviceid
[exp] => 1597130422
[iat] => 1597129822
[sub] => 000605.602acdd686cf407598d5277aa7fa7779.0934
[at_hash] => 9OuS7nLqLDWfFtfeP4iTpg
[email] => eitan@baconao.net
[email_verified] => true
[auth_time] => 1597129821
[nonce_supported] => 1
)