Skip to content

Commit a3ebb80

Browse files
author
Robert Wittman
committed
Added nonce to authentication process
1 parent 08b1949 commit a3ebb80

File tree

5 files changed

+136
-18
lines changed

5 files changed

+136
-18
lines changed

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ storage engines, this SDK will not store these state variables for you. However,
2727
functions for generating and managing them
2828

2929
```php
30-
\Shopify\Auth::generateNonce() // Returns a hashed string of <store>.<timestamp>, using API Secret as key
31-
// hash_hmac('sha256', <store>.<timestamp>, \Shopify\Shopify::api_secret());s
30+
\Shopify\Auth::generateNonce();
31+
// Returns a hashed string of <store>.<timestamp>, using API Secret as key
3232
```
3333
This will return a hashed string, composed by concatenating the store name with a timestamp, using the API Secret
3434
as the key.
@@ -46,11 +46,13 @@ This will return TRUE or FALSE, depending on if the nonce in the URL matches a n
4646
This function is automatically run during accessToken() in strict environments, so you shouldnt need to
4747
call it explicitly
4848

49+
For full example, check out [Full Auth Example with Comments](examples/authentication.php)
4950
### Authentication
5051

5152
To use the SDK for OAuth purposes, you need to provide your api_key, api_secret, permissions, and redirect_uri
5253

5354
```php
55+
// Set our options for initializing the SDK
5456
$options = array(
5557
'api_key' => 'some_random_api_key',
5658
'api_secret' => 'some_random_api_secret',
@@ -60,14 +62,19 @@ $options = array(
6062
);
6163
\Shopify\Shopify::init($options);
6264

63-
// Store this somewhere so we can compare it later
64-
$storageEngine->store($nonce);
6565

66-
if(isset($_GET['code']))
66+
if(!isset($_GET['code']))
6767
{
68+
$nonce = \Shopify\Auth::generateNonce();
69+
70+
// Store this somewhere so we can compare it later
71+
$storageEngine->store($nonce);
72+
6873
// Redirect to Shopify to start OAuth
6974
header("Location: ".\Shopify\Auth::authorizationUrl());
7075
} else {
76+
$nonce = $storageEngine->retrieve($nonce);
77+
\Shopify\Auth::setNonce($nonce);
7178

7279
// We can go ahead and get the access token
7380
echo \Shopify\Auth::accessToken();
@@ -167,7 +174,7 @@ try {
167174
\\ Exception\CurlException => cURL failed to connect
168175
\\ Exception\ApiException => There was an API error. [Invalid POST data, Invalid Endpoint, etc.]
169176

170-
```
177+
```/.lung`q23 45v
171178

172179
## References
173180

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
require_once 'path/to/vendor/autoload.php';
4+
5+
// Set our arguments for the SDK. These should be stored in another file
6+
$options = [
7+
'api_key' => '<shopify_api_key>',
8+
'api_secret' => '<shopify_api_secret>',
9+
'redirect_url' => 'https://localhost/shopify',
10+
'store' => '<store_name>',
11+
'permissions' => 'read_products',
12+
'strict' => FALSE // This disables response verifications
13+
];
14+
15+
// Initialize the SDK using arguments
16+
\Shopify\Shopify::init($options);
17+
18+
/* Now we check if we are starting an OAuth request request, or handling a
19+
response from Shopify. Checking for ?code=<auth_code> should suffice */
20+
if( !isset( $_GET['code'] ) )
21+
{
22+
header("Location: ".\Shopify\Auth::authorizationUrl());
23+
}
24+
else
25+
{
26+
try {
27+
$accessToken = \Shopify\Auth::accessToken();
28+
} catch(Exception $e) {
29+
echo $e->getMessage();
30+
exit();
31+
}
32+
33+
echo $accessToken; // 53e20e750c89274d02b53927135fd664
34+
}

examples/strict_authentication.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
require_once 'path/to/vendor/autoload.php';
4+
5+
// Initialize our storage engine. Replace this with whichever storage mechanism you want to use
6+
$storageEngine = new StorageEngine();
7+
// Set our arguments for the SDK. These should be stored in another file
8+
$options = [
9+
'api_key' => '<shopify_api_key>',
10+
'api_secret' => '<shopify_api_secret>',
11+
'redirect_url' => 'https://localhost/shopify',
12+
'store' => '<store_name>',
13+
'permissions' => 'read_products',
14+
];
15+
16+
// Initialize the SDK using arguments
17+
\Shopify\Shopify::init($options);
18+
19+
/* Now we check if we are starting an OAuth request request, or handling a
20+
response from Shopify. Checking for ?code=<auth_code> should suffice */
21+
if( !isset( $_GET['code'] ) )
22+
{
23+
// We need to initiate the OAuth flow. Generate a nonce we can store for
24+
// security purposes
25+
$nonce = \Shopify\Auth::generateNonce();
26+
27+
// In order to compare the nonce when they return, it needs to be stored somewhere.
28+
$storageEngine->store('auth_nonce', $nonce);
29+
30+
// We can now send the user to Shopify for authentication
31+
header("Location: ".\Shopify\Auth::authorizationUrl());
32+
}
33+
else
34+
{
35+
// We are handling an authentication resposne from Shopify. We need to validate the
36+
// request, and then exchange the code for an access token
37+
38+
// First, we get the nonce we stored
39+
$nonce = $storageEngine->retrieve('auth_nonce');
40+
// Set the nonce property of \Shopify\Auth so it can compare response -> request
41+
\Shopify\Auth::setNonce($nonce);
42+
// Finally, attempt to get the access token. If there is no nonce present, or the
43+
// store nonce does not match the one present in Shopify's response, or the HMAC
44+
// signature fails, this will throw an exception
45+
try {
46+
$accessToken = \Shopify\Auth::accessToken();
47+
} catch(Exception $e) {
48+
echo $e->getMessage(); // Authentication failed for some reason
49+
exit();
50+
}
51+
52+
echo $accessToken; // 53e20e750c89274d02b53927135fd664
53+
}
54+
55+
56+
/**
57+
* This is a very crude representation of session management.
58+
* This could be replaced with DB storage, redis, or your frameworks session library
59+
*
60+
* **For demo purposes only**
61+
*/
62+
class StorageEngine {
63+
public function __construct() {
64+
if (session_status() == PHP_SESSION_NONE) {
65+
session_start();
66+
}
67+
}
68+
69+
public function store($key, $value) {
70+
$_SESSION[$key] = $value;
71+
}
72+
73+
public function retrieve($key) {
74+
return $_SESSION[$key];
75+
}
76+
}

lib/Auth.php

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static function authorizationUrl()
3737
// Check if we require strict standards
3838
if(is_null(self::$nonce) && \Shopify\Shopify::strict())
3939
{
40-
throw new Exception\ApiException("Trying to use strict API without nonce");
40+
throw new Exception\ApiException('Strict API execution requires a nonce be set');
4141
}
4242
if(!is_null(self::$nonce)) $params['state'] = self::$nonce;
4343
return sprintf("https://%s/%s?%s", \Shopify\Shopify::store(), self::$auth_uri, http_build_query($params));
@@ -48,19 +48,20 @@ public static function authorizationUrl()
4848
* @param string $nonce
4949
* @return \Shopfiy\AccessToken
5050
*/
51-
public static function accessToken( $nonce = NULL )
51+
public static function accessToken()
5252
{
5353
// Do any strict checking for our OAuth requests
5454
if(\Shopify\Shopify::strict())
5555
{
56-
if( !self::checkNonce( $nonce ) ) throw new \Exception("Strict API execution requires a nonce for Authentication requests");
57-
if( !\Shopify\Shopify::validateHmac() ) throw new Exception\ApiException("Strict API execution requires a valid HMAC signature");
56+
if( is_null( self::$nonce ) ) throw new \Exception('No nonce was set. use Auth::setNonce($nonce) to set one');
57+
if( !self::checkNonce( $nonce ) ) throw new \Exception("Authentication nonce failed verification");
58+
if( !\Shopify\Shopify::validateHmac() ) throw new Exception\ApiException("HMAC signature failed verification");
5859
}
5960
return \Shopify\AccessToken::createFromCode($_GET['code']);
6061
}
6162

6263
/**
63-
* Set a nonce, used to secure API requests
64+
* Set the nonce property
6465
* @param string $nonce
6566
*/
6667
public static function setNonce($nonce)
@@ -73,7 +74,7 @@ public static function setNonce($nonce)
7374
* @param string $nonce
7475
* @return boolean
7576
*/
76-
public static function checkNonce($nonce = NULL)
77+
public static function checkNonce($nonce)
7778
{
7879
return $nonce === $_GET['state'];
7980
}
@@ -85,14 +86,14 @@ public static function getNonce()
8586

8687
/**
8788
* Create a nonce to be used for authentication
88-
*
89-
* This can be substituted with whichever technique a developer wants to implement
90-
*
9189
* @return string
9290
*/
93-
public static function generateNonce()
91+
public static function generateNonce( $save = TRUE )
9492
{
9593
if(is_null(\Shopify\Shopify::api_secret())) throw new Exception\ApiException("API Secret required for nonce generation");
96-
return hash_hmac('sha256', \Shopify\Shopify::store().'.'.time(),\Shopify\Shopify::api_secret() );
94+
$nonce = hash_hmac('sha256', \Shopify\Shopify::store().'.'.time(),\Shopify\Shopify::api_secret() );
95+
96+
if($save) self::setNonce($nonce);
97+
return $nonce;
9798
}
9899
}

lib/Http/Client.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ private function handleHttpCode($code)
186186
break;
187187
default: $msg = "An unknown error code [{$code}] was returned";
188188
}
189-
throw new \Shopify\Exception\ApiException($msg);
189+
throw new \Shopify\Exception\ApiException($msg, $code);
190190
}
191191
}
192192

0 commit comments

Comments
 (0)