3-Legged OAuth, with Services 7.x-3.3, REST Server using PHP
In Drupal with Services 3 modules, if you go to admin/structure/services/list/[endpoint name]/authentication you have choices under Default required authentication of unsigned, 2-legged and 3-legged.
What is 3-legged OAuth? Borrowing definitions from this blog:
3-legged OAuth describes the scenario for which OAuth was originally developed: a resource owner wants to give a client access to a server without sharing his credentials (i.e. username/password). A typical example is a user (resource owner) who wants to give a third-party application (client) access to his Twitter account (server) ... 2-legged OAuth , on the other hand, describes a typical client-server scenario, without any user involvement. An example for such a scenario could be a local Twitter client application accessing your Twitter account.
For Drupal, the only difference is selecting the option in your endpoint configuration.
The main difference occurs in your external application - PHP / Java or some other kind of client.
For 2-legged, your client application needs the consumer token and consumer secret to access your resource.
For 3-legged, your client application needs the consumer token and consumer secret to get request token and secret from your Drupal site, then redirect the user to the authorization page in your Drupal site, and afterwards gains the access token and secret to access your resource.
So there's a lot more work for 3-legged; use it only if your Drupal site do not trust the external application. Anyone who can offer a better explanation for 3-legged please edit this section.
I will now show you how to do 3-legged in Drupal with PHP as your client. The process seems hard as first, but once you do it, it becomes way easier the next time.
Installation
- Install Services module 7.x-3.3 - http://drupal.org/project/services
- Install Services Views module 7.x-1.0-beta2 - http://drupal.org/project/services_views
- Enable both modules and the REST server module.
- Install OAuth 7.x-3.0+18-dev - http://drupal.org/project/oauth (You must use this version because the recommended version - 7.x-3.0 - is bugged)
- Oauth is included in services 3.x
Note:
The Libraries module must be at least version 7.x-2.0 for REST server.
Config
This is a two-stage process.
First, make a Service Endpoint that hosts REST resources; it is seen as the first part of the URL to your resource.
Next, make a REST resource, which is a View customized by the Services View module; in it, you specify which Content Types are included.
Authentication is configured afterwards.
Config Endpoint / View without OAuth
( you may skip this if you have Services running already )
1. Add a service endpoint ( You may use an existing endpoint and skip to step 2 )
- Structure ➔ Services
- ✚Add
- Enter the name ( this is the first part of the URL to this service resource )
- Select REST under Server
- Enter the path, preferably the same as the name
- Save
2. Add a service view (Representing a single REST resource)
- Structure ➔ Views
- Add new view; check ONLY Create a block; enter name; continue & edit
- ✚Add ➔ Services ( if there is no Services, the services view module is not installed/enabled )
- FIELDS ➔ add; choose the content type to be included in the REST output
- Click / under SERVICE SETTINGS; enter the path for this view ( this is the second part of the URL to this service resource )
- Configure other options as needed
- Save
3. Configure the service endpoint
- Structure ➔ Services
- Edit Resources under OPERATIONS
- Check your service view name
- Save
- SERVER Tab ( you are currently in the RESOURCES tab )
- Choose your Response formats ( Checks only "json" if you want an JSON array )
- Save
Config OAuth
1. Enabling the modules
- Open modules
- Check OAuth under OAUTH
- Check OAuth Provider UI under OAUTH
- Check OAuth Authentication under SERVICES - AUTHENTICATION
- Configuration ➔ OAuth under WEB SERVICES; make sure Enable the oauth provider is checked
2. Add an OAuth Context
- Configuration ➔ OAuth ➔ ADD CONTEXT tab.
- Under SIGNATURE METHODS check HMAC-SHA1 and uncheck all the others
- Under AUTHORIZATION LEVELS enter name and title, preferably the same for both. Check Selected by default
- Save
3. Modifying the endpoint
- Structure ➔ Services
- Under OPERATIONS of the target endpoint select Edit
- Check OAuth authentication
- Save
- Under OPERATIONS of the target endpoint select Edit Authentication
- Under OAuth context select the name of your context
- Under Default required authentication select Consumer key and access token, also known as 3-legged OAuth
- Save
- Select your defined level under Default required OAuth Authorization level
- Save
4. Add an OAuth Consumer (Representation of your external application within OAuth)
- People ➔ ✚ Add user
- Enter a name; for example: "OAuth User"; e-mail and password are not used, so enter anything
- Save
- View your new user's profile by clicking on its name
- Authorization ➔ Add consumer
- Name your consumer something related to your external application that will use the REST resource. For example: frontpage_news
- The callback URL is not used. Select your context. Save
5. Update Drupal Permission
- People ➔ Permissions tab; scorll to OAuth
- Check all boxes for Register OAuth consumers in [your context name] and Authorize OAuth consumers in [your context name]
- Save
Given the correct setup, your resource is available at the following URL:
you Drupal site base URL / path to an endpoint / path to a view
for example: www.justinbieber.com/bieber/fever
However, if you browse to it you will see "request must be signed" message. You need PHP to sign the request:
PHP
Installation
pecl and phpize are required for OAuth. Open a terminal and type pecl and phpize to test if they are installed.
wget http://pear.php.net/go-pear.phar
php go-pear.phar
apt-get install php5-dev
pecl install phpize
pecl install oauth
Afterward, you must add to php.ini this line extension=oauth.so
In php.ini, search for "extension" and paste the line there.
If you have more than one php.ini, add to all of them.
Usage
Make a new PHP file. For example auth.php; put it somewhere you can access.
Start a session, 'cause PHP needs to remember tokens and secrets over a few requests:
session_start();
Define your Drupal URLs related to 3-legged OAuth;
change www.justinbieber.com to your site's address (unless that is your site's address; nothing wrong with that), but do not change oauth/* , that part is the same for everyone.
$request_token_url = 'http://www.justinbieber.com/oauth/request_token';
$access_token_url = 'http://www.justinbieber.com/oauth/access_token';
$authorize_url = 'http://www.justinbieber.com/oauth/authorize';
$resource_url = 'http://www.justinbieber.com/bieber/fever';
Get consumer key and secret from Drupal
- People ➔ [Your OAuth User] ➔ Authorization ➔ Consumers ➔ Edit under your external application's Consumer
- Copy the secret and key into PHP
$consumer_key = '8NMxrPkxdU8GAaLvsaP5qDKeLdVfRvWr';
$consumer_secret = '2G79cLpxsH4tuL2Mtv6G9h9QURGjMb3R';
Instantiate new OAuth object
$oauth = new OAuth($consumer_key, $consumer_secret, OAUTH_SIG_METHOD_HMACSHA1, OAUTH_AUTH_TYPE_URI);
$oauth->enableDebug(); // let you know when and why some request fails
Fetch the request token
if (empty($_SESSION['request_token_secret'])) {
$request_token_array = $oauth->getRequestToken($request_token_url);
$_SESSION['request_token'] = $request_token_array['oauth_token'];
$_SESSION['request_token_secret'] = $request_token_array['oauth_token_secret'];
header("Location: {$authorize_url}?oauth_token=" . $_SESSION['request_token']); // takes you to Drupal authorize page
} else if (empty($_SESSION['access_token']) { // continues below
Fetch access token / secret after authroization
$request_token_secret = $_SESSION['request_token_secret']; // PHP still needs the request token/secret to get the access token and secret
$oauth->setToken($_REQUEST['oauth_token'],$request_token_secret);
$access_token_info = $oauth->getAccessToken($access_token_url);
$_SESSION['access_token']= $access_token_info['oauth_token'];
$_SESSION['access_token_secret']= $access_token_info['oauth_token_secret'];
}
Finally, what you want, fetch your REST resource
if (isset ($_SESSION['access_token']))
{
$access_token = $_SESSION['access_token'];
$access_token_secret =$_SESSION['access_token_secret'];
$oauth->setToken($access_token,$access_token_secret);
$data = $oauth->fetch($resource_url); // all that hard work just to get this line working
$response_info = $oauth->getLastResponse();
echo "<pre>";
print_r(json_decode($response_info));
echo "</pre>";
}
Now save the file and navigate to this PHP file with Chrome or Firefox.
Save the access token and secret; you can reuse them to access your resource from other applications.
How to parse the JSON array
$array = json_decode($response_info);
if ($array) {
$object = $array[0]; // The array could contain multiple instances of your content type
$title = $object->title; // title is a field of your content type
}
Possible Errors:
Invalid auth/bad request (got a 401, expected HTTP/1.1 20X or a redirect)
Could happen with request token or access token. Make sure you have the right consumer secret / token, right URLs and you're using 3-legged not 2-legged.
More errors will be added as they come.
Happy Drupaling!
Oauth3legged_client module
Services 3.x supports oauth authentication while it is just a single server solution. For a complete oauth process, you will have user (Client), oauth consumer and oauth provider. In a production environment, it usually comes with 2 servers.
With the source code provided above, we create oauth3legged_client module to map access_token in user profile and saving it at a remote Drupal server. So that, it can regularly consume web services from oauth provider.
Firstly, you have to complete the setup above to create an oauth provider server. Then, install oauth3legged_client on another server with configuration below:
CONFIGURATION
____________________
To configure this module do the following:
1. Go to People -> Permissions (admin/people/permissions) and find the relevant module permissions underneath the "administer Oauth provider consumer. " section. If you are not logged in as user #1, you must give at least one role (probably the administrator role) the 'administer Oauth provider consumer. ' permission to configure this module.
2. Create required field in user profile by account settings -> manage field with field_token_key, field_token_secret, field_token_type etc.
3. Go to Configuration -> Oauth consumer configuration (admin/config/services/oauth3legged_client) and configure the module settings per your requirements. Add the field names created at 2 here. You will need key & secret generated by oauth provider server.
4. Using a web service client / mobile client (ios in my case) to test connections.
5. You need to configure oauth provider callback URL back to the server installed this module with
Example:
URL: dios://Oauth_consumer_server(server have oauth3legged_client module)/oauth3legged_client/oauthstart/authenticated/token
*authenticated/token is a temp fix to let this module knows the request right now is coming back from oauth server. dios is your custom URL schema. Please kindly update the document if you have a better solution.
6. In 5, we are using an ios client with drupal-for-ios library, we also need a url converter to change dios to http. Below is a sample code of ios handleOpenURL
Auth process
Step 1: (ios) client call oauth consumer (oauth3legged_client module)
Step 2: oauth consumer redirect client to oauth provider (input in configuration page) with request_token
Step 3: oauth provider identify the person with local authentication
Step 4: Return to oauth consumer server by callback URL with custom URL schema and access_token in $_SESSION
Step 5: The custom schema triggered an action on the ios client handleOpenURL, it analysis the request and replace the custom schema with http
Step 6: Replacing schema on ios with http triggers the oauth3legged_client again and save the access_token
Step 7: oauth3legged_client replace the schema to custom schema again to inform ios client to close the UIwebview
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
NSString *str = [url absoluteString];
NSLog(@"%@", url);
//success_dios is just an indicator to let oauth consumer know the process is finished
//Then close the UIWebview
if ([str rangeOfString:@"success_dios"].location != NSNotFound) {
NSLog(@"The whole 3 legged flow success %@", url);
[oauthWebView removeFromSuperview];
return YES;
}
str = [str stringByReplacingOccurrencesOfString:@"dios://"
withString:@"http://"];
NSLog(@"%@", str);
NSString *urlToLoad = [NSString stringWithFormat:@"%@", str ];
NSURL *url_return = [NSURL URLWithString:urlToLoad];
NSLog(@"loading url :%@", urlToLoad);
//URL Requst Object
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url_return];
//Load the request in the UIWebView.
[oauthWebView loadRequest:requestObj];
return YES;
}
Q&A:
Q: Why not just input callback URL as http schema so that it calls the oauth3legged_client module directly ?
A: With hundred times of test, directly trigger oauth3legged_client action from oauth_provider, then convert schema to custom schema and let ios client close UIWebview will hang up the process. Any better workaround is appreciated :) Please verify and edit the document
Q: Any workaround without custom schema and let drupal-for-ios remove the UIWebview ?
A: Please kindly let me know if you have a better solution :)
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion