My favorites | Sign in
Project Logo
                
Search
for
Updated Nov 09, 2007 by jon.r.crosby
Labels: Featured, Phase-Implementation
UsingOAuthConsumer  
A developer's guide to using OAuthConsumer in a Mac application.

Introduction

This guide is for developers interested in adding support for OAuth to their Mac apps. In order to use the framework, you must first get a copy from the svn repository and compile it. Compiling the framework will automatically run all of the unit tests.

Importing the Framework

There are 3 steps for adding the pre-compiled framework to your app in Xcode:

  1. Drag the framework into your Linked Frameworks group in Xcode, choosing to copy the framework instead of simply referencing it.
  2. Create a new Copy Files Build Phase for your app's main target.
  3. Drag the framework from the Linked Frameworks folder into the new Copy Files Build Phase and select "Frameworks" as its destination.

For now, please use this as a private embedded framework rather than copying it to the system's framework folder upon installation.

Using OAuthConsumer

Set Up

The first thing you will need in order to access a service providing OAuth support is a Consumer Key and a Consumer Secret. These must be obtained directly from the service provider. The OAuth Core 1.0 Spec does not address automatically generating such items.

The second thing required is a set of URLs provided for OAuth interactions by your service provider. These should be documented by the service provider and include:

  • Request Token URL
  • User Authorization URL
  • Access Token URL

Additionally, there should be a set of API standards to use in order to interact with the service after obtaining the OAuth credentials.

Once you have obtained your Consumer Key, Consumer Secret, and know your OAuth endpoint URLs, you are ready to begin writing code.

Getting an Unauthorized Request Token

The first order of business when communicating with an OAuth service provider is obtaining an Unauthorized Request Token. Using your Consumer Key, Consumer Secret, and the service provider's Request Token URL, you can send a request for an Unauthorized Request Token like this:

    OAConsumer *consumer = [[OAConsumer alloc] initWithKey:@"mykey"
                                                    secret:@"mysecret"];

    NSURL *url = [NSURL URLWithString:@"http://example.com/get_request_token"];

    OAMutableURLRequest *request = [[OAMutableURLRequest alloc] initWithURL:url
                                                                   consumer:consumer
                                                                      token:nil   // we don't have a Token yet
                                                                      realm:nil   // our service provider doesn't specify a realm
                                                          signatureProvider:nil]; // use the default method, HMAC-SHA1

    [request setHTTPMethod:@"POST"];

    OADataFetcher *fetcher = [[OADataFetcher alloc] init];

    [fetcher fetchDataWithRequest:request
                         delegate:self
                didFinishSelector:@selector(requestTokenTicket:didFinishWithData:)
                  didFailSelector:@selector(requestTokenTicket:didFailWithError:)];

Here is an example of a delegate method that uses a successful response to create the Request Token:

    - (void)requestTokenTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data {
        if (ticket.didSucceed) {
            NSString *responseBody = [[NSString alloc] initWithData:data
                                                           encoding:NSUTF8StringEncoding];
            requestToken = [[OAToken alloc] initWithHTTPResponseBody:responseBody];
        }
    }

Authorizing the Request Token

Now that you have an unauthorized request token, direct your user to the service provider's User Authorization URL so that they can approve access for your application:

    NSURL *url = [NSURL URLWithString:@"http://example.com/authorize"];
    [[NSWorkspace sharedWorkspace] openURL:url];

The service provider should first ensure the user is logged in, requiring authentication if this is not the case. The user will then have an opportunity to grant access for your application on the service provider's site. Service providers can use this interaction to limit access to certain kinds of assets if desired.

Obtaining an Access Token

Now that the Request Token has been authorized, an Access Token can be obtained in the same manner as described in "Getting an Unauthorized Request Token" with two exceptions:

  1. Instead of passing nil for the token parameter when creating the OAMutableURLRequest, pass the token obtained with the requestTokenTicket:didFinishWithData: method, above.
  2. The delegate methods might choose different names such as accessTokenTicket:didFinishWithData: and accessTokenTicket:didFailWithError:.

The delegate method can create the Access Token as an OAToken object in exactly the same manner as described above for the Request Token.

Using the Keychain

If you choose to store this token in the user's Keychain, it can be done like this:

    [accessToken storeInDefaultKeychainWithAppName:@"MyApp"
                               serviceProviderName:@"Example.com"];

Retrieving that same token from the Keychain on a different run of your app looks like this:

    OAToken *accessToken = [[OAToken alloc] initWithKeychainUsingAppName:@"MyApp"
                                                     serviceProviderName:@"Example.com"];

Accessing Protected Resources

At this point, you are ready to access the service provider's APIs, authenticating with OAuth in the background. You can either send requests using OAMutableURLRequest in the same manner as you would normally do with NSMutableURLRequest or you can continue to use the convenience objects OAServiceTicket and OADataFetcher. If you choose the former, be sure to call the prepare method on the request prior to initiating the connection or your request will not contain valid OAuth parameters.

Speaking of parameters, there are methods added to NSMutableURLRequest (and thus OAMutableURLRequest) to support setting parameters for requests. At the moment, there is a requirement to first set the HTTP method, then add or get the parameters as an NSArray.

Here is an example OAuth request for a protected resource using the GET method (default), HTTPS, the PLAINTEXT signature method, passing two parameters:

  1. title = My Page
  2. description = My Page Holds Text
    NSURL *url = [NSURL URLWithString:@"https://example.com/user/1/flights/"];
    OAMutableURLRequest *request = [[OAMutableURLRequest alloc] initWithURL:url
                                                                   consumer:consumer
                                                                      token:accessToken
                                                                      realm:nil
                                                          signatureProvider:[[OAPlaintextSignatureProvider alloc] init]];
    
    OARequestParameter *nameParam = [[OARequestParameter alloc] initWithName:@"title"
                                                                       value:@"My Page"];
    OARequestParameter *descParam = [[OARequestParameter alloc] initWithName:@"description"
                                                                       value:@"My Page Holds Text"];
    NSArray *params = [NSArray arrayWithObjects:nameParam, descParam, nil];
    [request setParameters:params];
    
    OADataFetcher *fetcher = [[OADataFetcher alloc] init];
    [fetcher fetchDataWithRequest:request
                         delegate:self
                didFinishSelector:@selector(apiTicket:didFinishWithData:)
                  didFailSelector:@selector(apiTicket:didFailWithError:)];

Comment by marcmp, Nov 26, 2008

Marc M:: It's essential that you set the type of request (POST, GET, DELETE, ...) before setting any parameters. Otherwise they will sit in the wrong place.

Comment by kimptoc, Apr 24, 2009

Hi, thanks for the code. I am probably missing something (in oauth/web basics), but in a client app, what ties the user to the access token - I would have thought you'd need to pass in the username or similar in obtaining access token.

Or is the unauth req token passed in the "Authorizing the Request Token" step?

Thanks, Chris

Comment by dileep.y, Jun 01, 2009

how to download the whole framework??

Comment by kimptoc, Jun 20, 2009

Stating the obvious, probably, but with the latest OAuth spec change, 1.0a, I guess the code needs an update to pass in the oauth_verifier which the host will give the consumer... (ie pincode from twitter).

http://oauth.googlecode.com/svn/spec/core/1.0a/drafts/3/oauth-core-1_0a.html#auth_step3

Thanks, Chris

Comment by kimptoc, Jun 21, 2009

Further to my comment yesterday, the changes I made to make this work with 1.0a was to add a verifier string to OAToken and then in the OAMutableURLRequest prepare method add it to the request, as follows:

@@ -141,7 +141,8 @@ signatureProvider:(id<OASignatureProviding, NSObject>)aProvider
     if ([token.key isEqualToString:@""])
         oauthToken = @""; // not used on Request Token transactions
     else
-        oauthToken = [NSString stringWithFormat:@"oauth_token=\"%@\", ", [token.key URLEncodedString]];
+        oauthToken = [NSString stringWithFormat:@"oauth_token=\"%@\", oauth_verifier=\"%@\", ", 
+                                         [token.key URLEncodedString], [token.verifier URLEncodedString]];
     

Regards, Chris

Comment by ord...@yourhead.com, Jun 25, 2009

Hi,

I borrowed heavily from Chris Kimpton's work and built a complete project out of it. Included all of the frameworks etc.

This means that you can click the download button. Open the project. Push run.

I did change one substantial thing. I subclass the MGTwitterEngine instead of modifying it. That way we don't have to fork MGTwitterEngine and can stay current with Matt's work and others' contributions.

My solution is to include an internal web-view into the project to display the "Accept" page. This basically replaces the "username/password" view that would normally be in a project.

I've opened all the source, and put it on github. Please feel free to use it, modify it, or comment on it. Thanks again to Chris and Matt for making this possible.

http://github.com/yourhead/OAuth_ObjC_Test_App/tree/master

Thanks, Isaiah YourHead? Software http://www.yourhead.com

Comment by mrjjwright, Jul 11, 2009

Hey Isaiah,

Thanks for the cool project bringing this together. I notice however that your code doesn't seem to take the user's pin that is displayed by Twitter after successful authentication, and I can't seem to authenticate with Twitter without it. Is there something I am missing? I get this error in the console after the pin is displayed (it goes away automatically):

2009-07-11 15:43:52.228 oauth_test_app59733:10b? fail: 'Error Domain=NSURLErrorDomain Code=-1012 UserInfo?=0x10a9b20 "Operation could not be completed. (NSURLErrorDomain error -1012.)"'

Comment by fares.farhan, Jul 22, 2009

Hi,

As mrjjwright has mentioned, its a great sample you've bring Isaiah, but I also can't figure it out when Twitter replied successful registration and then respond with PIN and additional message "now simply back to application and enter the PIN". Where do this PIN need to be included, another URL post to Twitter or something else needs to be done first?

Cheers, Fares

Comment by pchukwura, Jul 24, 2009

When it comes to the Twitter PIN the user receives, I have my application expect the PIN the user gets after a successful request is made to get the request token.

so basically follow the steps of this wiki but do this right before the Obtaining an Access Token step:

Get the PIN number from the NSTextField that the user entered and set it to the 'verifier' property of the token...

[requestToken setValue:[twitterPIN stringValue] forKey:@"verifier"];
		url = [NSURL URLWithString:@"http://twitter.com/oauth/access_token"];

This is assuming that OAToken has a 'verifier' property (which I added). Then continue with the steps in this wiki.

Hope this helps.

Comment by akc1085, Jul 29, 2009

Is anyone else having trouble getting this to work after twitter's last update on July-27-2009? The update is supposed to prevent invalid signatures from being accepted.

Comment by pchukwura, Jul 29, 2009

yes I am having toruble. I keep getting "invalid/used nonce" error when trying to do a status update from Twitter, even though the nonce is indeed unique. Has anyone solved this issue?

Comment by pchukwura, Jul 29, 2009

If anyone else had the same issues I had, i was able to resolve them... i posted the issue, and fix on my blog. http://nullagenda.com/oauthconsumer-and-twitter-114

Comment by kimptoc, Jul 30, 2009

Same problem here, but fixed very quickly thanks to pchukwura, thanks for sharing the solution. For the impatient, just add an extra 'if' clause to only add the whole verifier clause if the verifier value is set, ie dont include the verifier= bit if no verifier...

Comment by zuksh.5, Aug 04, 2009

Where do we find this OAuthConsumer.Framework ? One in the repository wont compile. Help !!! any help regarding this issue will be highly appreciated.

Comment by pchukwura, Aug 04, 2009

What compile are you seeing?

Comment by zuksh.5, Aug 05, 2009

i resolved that issue ... now this one .... kindly reply soon, i cannot understand the cause of this error

dyld: Library not loaded: @executable_path/../Frameworks/OAuthConsumer.framework/Versions/A/OAuthConsumer

Referenced from: /Users/dssd/Desktop/MGTwitterEngine/MGTwitterEngine/build/Debug/MGTwitterEngine.app/Contents/MacOS/MGTwitterEngine Reason: image not found

---

Comment by mon.casier, Aug 12, 2009

Hmm, looks like I didn't get some point:

1. I'm creating a OAMutableURLRequest 2. fulfill httpMethod amd headers 3. I can choose - use native SDK NSURLConnection or proposed OADataFetcher

I already have a valid access token and can't make queries with it through native SDK:

But I'm getting refuse from server (Couldn't authenticate you)

         NSString* strUrl = [[[NSString alloc] initWithFormat:@"%@%@", apiServer, requestUrl] autorelease];

	

	NSURL* url = [NSURL URLWithString:strUrl]; 

	

	OAMutableURLRequest *request = [[OAMutableURLRequest alloc] initWithURL: url

                                                                        consumer: consumer

                                                                        token: accessToken

                                                                        realm: nil

                                                                        signatureProvider: [[OAPlaintextSignatureProvider alloc] init]];

	

	[request setHTTPMethod: method];

	

	if(body != nil) {

		NSData* dataRequestBody = [body dataUsingEncoding:NSUTF8StringEncoding];

		[request setHTTPBody:dataRequestBody];

	}

	

	[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

	

	if( [method compare:@"PUT"] ==  NSOrderedSame ) {

		[request setValue:@"PUT" forHTTPHeaderField:@"X_HTTP_METHOD_OVERRIDE"];

	}

	

	[request prepare];

	

	NSHTTPURLResponse* resp = nil; 

	NSError* err = nil;

    

    NSData *serverData = [NSURLConnection sendSynchronousRequest: request   returningResponse: &resp  error: &err];

	SEL selector = (serverData != nil) ? success : fail;

	

	NSObject* param = (serverData != nil) ? (NSObject*) serverData : (NSObject*) err; 

	

	if(selector != nil) {

		if( object != nil) {

			[self performSelector: selector withObject: object  withObject: param ];

		} else {

			[self performSelector: selector withObject: param ];

		}

	}

this is app log:


2009-08-12 21:32:14.495 MyTool[4469:20b] String: PUT&http%3A%2F%2F10.1.1.1%2Fapi%2F0.9%2Fdataset%2Fcreate&%253Cdata%253E%253Cdataset%253E%253Cdata%2520k%3D%2522created_by%2522%2520v%26oauth_consumer_key%3DdwntFN9NJv9HrvMuqNgWbA%26oauth_nonce%3D067BD7D5-9C90-47EC-B19E-E8ACED9472E0%26oauth_signature_method%3DPLAINTEXT%26oauth_timestamp%3D1250101934%26oauth_token%3DubZlywqgHWaaoUlPqdDnvQ%26oauth_version%3D1.0, Array: %253Cosm%253E%253Cdataset%253E%253Cdata%2520k=%2522created_by%2522%2520v&oauth_consumer_key=dwntFN9NJv9HrvMuqNgWbA&oauth_nonce=067BD7D5-9C90-47EC-B19E-E8ACED9472E0&oauth_signature_method=PLAINTEXT&oauth_timestamp=1250101934&oauth_token=ubZlywqgHWaaoUlPqdDnvQ&oauth_version=1.0
2009-08-12 21:32:14.496 MyTool[4469:20b] Headers: {
    Authorization = "OAuth realm=\"\", oauth_consumer_key=\"dwntFN9NJv9HrvMuqNgWbA\", oauth_token=\"ubZlywqgHWaaoUlPqdDnvQ\", oauth_signature_method=\"PLAINTEXT\", oauth_signature=\"3iF9JIzYQPT5JkvrEXHujCLhI03An302P94JeQoF8%26minE6ZinCiaNlCFfbazOamC1DsD3FcvVF4C1fM5k\", oauth_timestamp=\"1250101934\", oauth_nonce=\"067BD7D5-9C90-47EC-B19E-E8ACED9472E0\", oauth_version=\"1.0\"";
    "Content-Type" = "application/x-www-form-urlencoded";
    "X_http_method_override" = PUT;
}
Comment by mon.casier, Aug 13, 2009

GUYS THERE IS A BUG!

IF YOU USING REQUEST BODY ( setHTTPBody: body?; )

FIRST - SIGN THE REQUEST ( eg prepare?;) and then SET THE BODY

OR fix it in signatureBaseString for OAMutableURLRequest


Sign in to add a comment
Hosted by Google Code