Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
dev:oauth [2011/02/07 01:25] sean |
dev:oauth [2017/11/12 19:53] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== OAuth Key Exchange ====== | + | <html><p id="zotero-5-update-warning" style="color: red; font-weight: bold">We’re |
+ | in the process of updating the documentation for | ||
+ | <a href="https://www.zotero.org/blog/zotero-5-0">Zotero 5.0</a>. Some documentation | ||
+ | may be outdated in the meantime. Thanks for your understanding.</p></html> | ||
- | In addition to users manually creating Zotero API keys from the zotero.org account settings, Zotero supports [[http://oauth.net/|OAuth]] for API key exchange. | ||
- | ===== Registering Your Application ===== | + | See [[dev/web_api/oauth]]. |
- | + | ||
- | In order to start using OAuth to create API keys on behalf of users, you must [[/oauth/apps|register your application with Zotero]] to obtain a Client Key and Client Secret for use during all future OAuth handshakes between your application/website and zotero.org. Note that after you obtain an API key for a particular user these client credentials are not required for further Zotero API requests. | + | |
- | + | ||
- | ===== Using OAuth Handshake for Key Exchange ===== | + | |
- | + | ||
- | Below is an example php file implementing the application side of the OAuth handshake with zotero.org and using the key obtained to make a request to the Zotero API. | + | |
- | + | ||
- | + | ||
- | <?php | + | |
- | /** Note that this example uses the php OAuth extension http://php.net/manual/en/book.oauth.php | + | |
- | * but there are various php libraries that provide similar functionality. | + | |
- | * OAuth acts over multiple pages, so we save variables we need to remember in $state in a temp file | + | |
- | * | + | |
- | * The OAuth handshake has 3 steps: | + | |
- | * 1: Make a request to the provider to get a temporary token | + | |
- | * 2: Redirect user to provider with a reference to the temporary token. The provider will ask them to authorize it | + | |
- | * 3: When the user is sent back by the provider and the temporary token is authorized, exchange it for a permanent | + | |
- | * token then save the permanent token for use in all future requests on behalf of this user. | + | |
- | * | + | |
- | * So an OAuth consumer needs to deal with 3 states which this example covers: | + | |
- | * State 0: We need to start a fresh OAuth handshake for a user to authorize us to get their information. | + | |
- | * We get a request token from the provider and send the user off to authorize it | + | |
- | * State 1: The provider just sent the user back after they authorized the request token | + | |
- | * We use the request token + secret we stored for this user and the verifier the provider just sent back to | + | |
- | * exchange the request token for an access token. | + | |
- | * State 2: We have an access token stored for this user from a past handshake, so we use that to make data requests | + | |
- | * to the provider. | + | |
- | **/ | + | |
- | //initialize some variables to start with. | + | |
- | //clientkey, clientSecret, and callbackurl should correspond to http://www.zotero.org/oauth/apps | + | |
- | $clientKey = '9c6221a6ccae7639711a'; | + | |
- | $clientSecret = '39091046dc9cf4dc3b61'; | + | |
- | $callbackUrl = 'http://localhost/oauthtestentry.php'; | + | |
- | //the endpoints are specific to the OAuth provider, in this case Zotero | + | |
- | $request_token_endpoint = 'https://www.zotero.org/oauth/request'; | + | |
- | $access_token_endpoint = 'https://www.zotero.org/oauth/access'; | + | |
- | $zotero_authorize_endpoint = 'https://www.zotero.org/oauth/authorize'; | + | |
- | //Functions to save state to temp file between requests, DB should replace this functionality | + | |
- | function read_state(){ | + | |
- | return unserialize(file_get_contents('/tmp/oauthteststate')); | + | |
- | } | + | |
- | function write_state($state){ | + | |
- | file_put_contents('/tmp/oauthteststate', serialize($state)); | + | |
- | } | + | |
- | function save_request_token($request_token_info, $state){ | + | |
- | // Make sure the request token has all the information we need | + | |
- | if(isset($request_token_info['oauth_token']) && isset($request_token_info['oauth_token_secret'])){ | + | |
- | // save the request token for when the user comes back | + | |
- | $state['request_token_info'] = $request_token_info; | + | |
- | $state['oauthState'] = 1; | + | |
- | write_state($state); | + | |
- | } | + | |
- | else{ | + | |
- | die("Request token did not return all the information we need."); | + | |
- | } | + | |
- | } | + | |
- | function get_request_token($state){ | + | |
- | if($_GET['oauth_token'] != $state['request_token_info']['oauth_token']){ | + | |
- | die("Could not find referenced OAuth request token"); | + | |
- | } | + | |
- | else{ | + | |
- | return $state['request_token_info']; | + | |
- | } | + | |
- | } | + | |
- | function save_access_token($access_token_info, $state){ | + | |
- | if(!isset($access_token_info['oauth_token']) || !isset($access_token_info['oauth_token_secret'])){ | + | |
- | //Something went wrong with the access token request and we didn't get the information we need | + | |
- | throw new Exception("OAuth access token did not contain expected information"); | + | |
- | } | + | |
- | //we got the access token, so save it for future use | + | |
- | $state['oauthState'] = 2; | + | |
- | $state['access_token_info'] = $access_token_info; | + | |
- | write_state($state); //save the access token for all subsequent resquests, in Zotero's case the token and secret are just the same Zotero API key | + | |
- | } | + | |
- | function get_access_token($state){ | + | |
- | if(empty($state['access_token_info'])){ | + | |
- | die("Could not retrieve access token from storage."); | + | |
- | } | + | |
- | return $state['access_token_info']; | + | |
- | } | + | |
- | //Initialize our environment | + | |
- | //check if there is a transaction in progress | + | |
- | //for testing purpose, start with a fresh state to perform a new handshake | + | |
- | if(empty($_GET['reset']) && file_exists('/tmp/oauthteststate')){ | + | |
- | $state = read_state(); | + | |
- | } | + | |
- | else{ | + | |
- | $state = array(); | + | |
- | $state['localUser'] = 'localUserInformation'; | + | |
- | $state['oauthState'] = 0; //we do not have an oauth transaction in process yet | + | |
- | write_state($state); | + | |
- | } | + | |
- | // If we are in state=1 there should be an oauth_token, if not go back to 0 | + | |
- | if($state['oauthState'] == 1 && !isset($_GET['oauth_token'])){ | + | |
- | $state['oauthState'] = 0; | + | |
- | } | + | |
- | //Make sure we have OAuth installed depending on what library you're using | + | |
- | if(!class_exists('OAuth')){ | + | |
- | die("Class OAuth does not exist. Make sure PHP OAuth extension is installed and enabled."); | + | |
- | } | + | |
- | //set up a new OAuth object initialized with client credentials and methods accepted by the provider | + | |
- | $oauth = new OAuth($clientKey, $clientSecret, OAUTH_SIG_METHOD_HMACSHA1, OAUTH_AUTH_TYPE_FORM); | + | |
- | $oauth->enableDebug(); //get feedback if something goes wrong. Should not be used in production | + | |
- | //Handle different parts of the OAuth handshake depending on what state we're in | + | |
- | switch($state['oauthState']){ | + | |
- | case 0: | + | |
- | // State 0 - Get request token from Zotero and redirect user to Zotero to authorize | + | |
- | try{ | + | |
- | $request_token_info = $oauth->getRequestToken($request_token_endpoint, $callbackUrl); | + | |
- | } | + | |
- | catch(OAuthException $E){ | + | |
- | echo "Problem getting request token<br />"; | + | |
- | echo $E->lastResponse; echo "<br />"; | + | |
- | die; | + | |
- | } | + | |
- | save_request_token($request_token_info, $state); | + | |
- | + | ||
- | // Send the user off to the provider to authorize your request token | + | |
- | // This could also be a link the user follows | + | |
- | $redirectUrl = "{$zotero_authorize_endpoint}?oauth_token={$request_token_info['oauth_token']}"; | + | |
- | header('Location: ' . $redirectUrl); | + | |
- | break; | + | |
- | case 1: | + | |
- | // State 1 - Handle callback from Zotero and get and store an access token | + | |
- | // Make sure the token we got sent back matches the one we have | + | |
- | // In practice we would look up the stored token and whatever local user information we have tied to it | + | |
- | $request_token_info = get_request_token($state); | + | |
- | //if we found the temp token, try to exchange it for a permanent one | + | |
- | try{ | + | |
- | //set the token we got back from the provider and the secret we saved previously for the exchange. | + | |
- | $oauth->setToken($_GET['oauth_token'], $request_token_info['oauth_token_secret']); | + | |
- | //make the exchange request to the provider's given endpoint | + | |
- | $access_token_info = $oauth->getAccessToken($access_token_endpoint); | + | |
- | save_access_token($access_token_info, $state); | + | |
- | } | + | |
- | catch(Exception $e){ | + | |
- | //Handle error getting access token | + | |
- | die("Caught exception on access token request"); | + | |
- | } | + | |
- | // Continue on to authorized state outside switch | + | |
- | break; | + | |
- | case 2: | + | |
- | //get previously stored access token if we didn't just get it from a handshack | + | |
- | $access_token_info = get_access_token($state); | + | |
- | break; | + | |
- | } | + | |
- | // State 2 - Authorized. We have an access token stored already which we can use for requests on behalf of this user | + | |
- | echo "Have access token for user."; | + | |
- | //zotero will send the userID associated with the key along too | + | |
- | $zoteroUserID = $access_token_info['userID']; | + | |
- | //Now we can use the token secret the same way we already used a Zotero API key | + | |
- | $zoteroApiKey = $access_token_info['oauth_token_secret']; | + | |
- | $feed = file_get_contents("https://api.zotero.org/users/{$zoteroUserID}/items?limit=1&key={$zoteroApiKey}"); | + | |
- | var_dump($state); | + | |
- | echo "<pre>" . htmlentities($feed) . "</pre>"; | + | |
- | /** OAuth support for all api requests may be added in the future | + | |
- | * but for now secure https provides similar benefits anyway | + | |
- | */ | + | |
- | ?> | + |