Reputation: 780
I am following the 23andMe API authentication guidelines and sending my users to 23andMe for authentication and authorization using this code:
<?php
function base64url_encode($data)
{
$b64 = base64_encode($data);
if ($b64 === false) {
return false;
}
$url = strtr($b64, '+/', '-_');
return rtrim($url, '=');
}
$client_id = 'xxx';
$redirect_uri = 'https://customURL';
// this is needed for custom redirect scheme
$code_verifier = 'yyy';
$hash = hash('sha256', $code_verifier);
$code_challenge = base64url_encode(pack('H*', $hash));
header("Location: https://api.23andme.com/authorize/"
. "?redirect_uri=$redirect_uri"
. "&response_type=code"
. "&client_id=$client_id"
. "&scope=basic"
. "&code_challenge=$code_challenge");
?>
Using Chrome DevTools I can see the following 3 URLs are fetched once the above page is visited, and the user authorizes the access:
https://auth.23andme.com/authorize/?scope=openid&response_type=code&client_id=api&redirect_uri=https%3A%2F%2Fapi.23andme.com%2Foauth_callback%2F
https://api.23andme.com/oauth_callback/?code=30edd45deb5f42d4bb7bd8413866cfc5&state=
https://api.23andme.com/authorize/?response_type=code&client_id=xxx&redirect_uri=https://customURL&scope=basic&code_challenge=wAYGGEPHpGhaG1gZTmyJ8M1Ly7JlGuoUVWBBVJ4OxTU
So I can see that the code parameter has been generated (and in this case given the value 30edd45deb5f42d4bb7bd8413866
. This value should be given to the following code (as $_GET["code"]
) but it results in a 500 error:
<?php
$code = htmlspecialchars($_GET["code"]);
$code_verifier = 'yyy';
$post_field_array = array(
'client_id' => 'xxx',
'client_secret' => 'zzz',
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => 'https://customURL',
'scope' => 'basic',
'code_verifier' => $code_verifier);
// Encode the field values for HTTP.
$post_fields = '';
foreach ($post_field_array as $key => $value)
$post_fields .= "$key=" . urlencode($value) . '&';
$post_fields = rtrim($post_fields, '&');
// Use cURL to get the JSON response from 23andMe.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.23andme.com/token/');
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_POST, count($post_field_array));
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$encoded_json = curl_exec($ch);
$response = json_decode($encoded_json, true);
$access_token = $response['access_token'];
//show
print_r($encoded_json);
echo $access_token;
?>
Now the response with the 500 is:
https://api.23andme.com/authorize_check?response_type=code&client_id=xxx&redirect_uri=https%3A%2F%2FcustomURL&scope=basic&select_profile=false
Appreciate any suggestions for troubleshooting.
Upvotes: 1
Views: 220
Reputation: 42716
You have some issues around URL encoding; the question does not make clear exactly what your problem is, but I'll suggest making these changes.
In the first code block, you do no URL encoding. This can be a problem if one of your values contains an ampersand or other special character. Use http_build_query()
to build a query string safely. In addition, hash()
can output binary directly so there's no need to use pack()
on the result, and you should always exit after a redirection. PHP never needs a closing ?>
tag unless there is HTML following (which there shouldn't be.)
<?php
function base64url_encode($data)
{
$b64 = base64_encode($data);
if ($b64 === false) {
return false;
}
$url = strtr($b64, '+/', '-_');
return rtrim($url, '=');
}
$code_verifier = 'yyy';
$hash = hash('sha256', $code_verifier, true);
$query_string_vars = [
"redirect_uri" => "https://customURL",
"response_type" => "code",
"client_id" => "xxx",
"scope" => "basic",
"code_challenge" => base64url_encode($hash),
];
$url = "https://api.23andme.com/authorize/?" . http_build_query($query_string_vars);
header("Location: $url");
exit();
In your second code block, you are encoding values using urlencode()
but this is not necessary, as cURL will automatically encode values passed to CURLOPT_POSTFIELDS
as an array, or you can use http_build_query()
again to pass a string. In addition, you use htmlspecialchars()
on your code value for some reason, which is quite likely to break it.
<?php
$code_verifier = 'yyy';
$post_field_array = [
'client_id' => 'xxx',
'client_secret' => 'zzz',
'grant_type' => 'authorization_code',
'code' => $_GET["code"],
'redirect_uri' => 'https://customURL',
'scope' => 'basic',
'code_verifier' => $code_verifier
];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.23andme.com/token/',
CURLOPT_VERBOSE => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($post_field_array),
CURLOPT_RETURNTRANSFER => true,
]);
$json = curl_exec($ch);
$response = json_decode($json, true);
$access_token = $response['access_token'];
//show
print_r($json);
echo $access_token;
Upvotes: 1