Twitter + oAuth nightmare - iphone

I'm trying to implement a custom login view to Twitter (I don't want that UIWebView).
I've downloaded many classes, and I'm so far having a nightmare with this. Now I'm trying to make Twitter + oAuth work. Here's the demo code (that works):
_engine = [[SA_OAuthTwitterEngine alloc] initOAuthWithDelegate: self];
_engine.consumerKey = kOAuthConsumerKey;
_engine.consumerSecret = kOAuthConsumerSecret;
[_engine requestRequestToken];
UIViewController *controller = [SA_OAuthTwitterController controllerToEnterCredentialsWithTwitterEngine: _engine delegate: self];
if (controller)
[self presentModalViewController: controller animated: YES];
else
[_engine sendUpdate: [NSString stringWithFormat: #"Already Updated. %#", [NSDate date]]];
Now what I wanna do is replace that SA_OAuthTwitterController with custom UITextFields. So I'm trying this:
_engine = [[SA_OAuthTwitterEngine alloc] initOAuthWithDelegate: self];
_engine.consumerKey = kOAuthConsumerKey;
_engine.consumerSecret = kOAuthConsumerSecret;
[_engine requestRequestToken];
[_engine requestAccessToken];
[_engine setUsername:#"username" password:#"password"];
[_engine sendUpdate:#"tweet"];
But I keep getting error 401. I'm probably missing a step. Anyone?

The view controller you are eliminating loads a webview that prompts the user to authenticate your application with twitter. If you don't ever have the user authenticate, you won't be able to make authenticated twitter calls. There is a way to do what you are wanting, however, you have to go through all of the OAuth steps yourself. Here are the steps as specified from a different web service (Vimeo), but the same rules apply:
Your application sends a request
with your consumer key, and signed
with your consumer secret, for a
something called a request token. If
we verify your application
correctly, we'll send you back a
request token and a token secret.
You'll then create a link for the user to click on with the request token.
When the user gets to Vimeo, they'll be prompted to allow your
application access to their account.
If they click yes, we'll send them
back to your application, along with
a verifier.
You'll then use the request token, verifier, and token secret to make
another call to us to get an access
token. The access token is what
you'll use to access the user's
information on Vimeo.
OAuth is a real pain in the rump, so good luck. ;-)

I think the bit you're missing is here, SA_OAuthTwitterEngine.m:103:
//This generates a URL request that can be passed to a UIWebView. It will open a page in which the user must enter their Twitter creds to validate
- (NSURLRequest *) authorizeURLRequest {
if (!_requestToken.key && _requestToken.secret) return nil; // we need a valid request token to generate the URL
OAMutableURLRequest *request = [[[OAMutableURLRequest alloc] initWithURL: self.authorizeURL consumer: nil token: _requestToken realm: nil signatureProvider: nil] autorelease];
[request setParameters: [NSArray arrayWithObject: [[[OARequestParameter alloc] initWithName: #"oauth_token" value: _requestToken.key] autorelease]]];
return request;
}
You will have to "fake" this user login out yourself, I believe by sending twitter the login credentials as a separate request. It appears that the setUsername method you're calling is actually called as a post operation once a valid access token has been received. See SA_OAuthTwitterEngine.m:185
//
// access token callback
// when twitter sends us an access token this callback will fire
// we store it in our ivar as well as writing it to the keychain
//
- (void) setAccessToken: (OAServiceTicket *) ticket withData: (NSData *) data {
if (!ticket.didSucceed || !data) return;
NSString *dataString = [[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] autorelease];
if (!dataString) return;
if (self.pin.length && [dataString rangeOfString: #"oauth_verifier"].location == NSNotFound) dataString = [dataString stringByAppendingFormat: #"&oauth_verifier=%#", self.pin];
NSString *username = [self extractUsernameFromHTTPBody:dataString];
if (username.length > 0) {
[[self class] setUsername: username password: nil];
if ([_delegate respondsToSelector: #selector(storeCachedTwitterOAuthData:forUsername:)]) [(id) _delegate storeCachedTwitterOAuthData: dataString forUsername: username];
}
[_accessToken release];
_accessToken = [[OAToken alloc] initWithHTTPResponseBody:dataString];
}
So the steps are as follows:
Request request token
Fake user login to twitter and get the pin value
Set the pin on the engine
Request access token
You can see where SA_OAuthTwitterController parses the pin out of the webview content in SA_OAuthTwitterController.m:156
#pragma mark Webview Delegate stuff
- (void) webViewDidFinishLoad: (UIWebView *) webView {
NSError *error;
NSString *path = [[NSBundle mainBundle] pathForResource: #"jQueryInject" ofType: #"txt"];
NSString *dataSource = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (dataSource == nil) {
NSLog(#"An error occured while processing the jQueryInject file");
}
[_webView stringByEvaluatingJavaScriptFromString:dataSource]; //This line injects the jQuery to make it look better
NSString *authPin = [[_webView stringByEvaluatingJavaScriptFromString: #"document.getElementById('oauth_pin').innerHTML"] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (authPin.length == 0) authPin = [[_webView stringByEvaluatingJavaScriptFromString: #"document.getElementById('oauth_pin').getElementsByTagName('a')[0].innerHTML"] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
[_activityIndicator stopAnimating];
if (authPin.length) {
[self gotPin: authPin];
}
if ([_webView isLoading] || authPin.length) {
[_webView setHidden:YES];
} else {
[_webView setHidden:NO];
}
}
hope this helps.

You have to use xAuth for custom user/pass controls. But it requires the permission from Twitter.

Related

iphone - return fbid to use it in json web service to receive data

I've been looking around, saw similar posts, but nothing like this that could give me answers. This is my setup and flow of the app:
User has to login via Facebook, using Facebook Graph. LoginView is presented modally, non animated
When user logins I can retrieve FBID and I use this fbid to send it to my web service (REST)
Web service gets the FBID from the NSURL and matches it with database to retrieve other user info
Using JSONserialization i parse the JSON received from web service and display it in the view
PROBLEM: Everything returns NULL except FBID that I get from Facebook. BUT, if I logout from Facebook and then login, that's when it works.
Here is my code:
viewDidAppear method:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:NO];
if (FBSession.activeSession.isOpen) {
[self populateUserDetails];
}
//Connect to WebService
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://atnightcorp.com/api/member/id/%#/format/json", fbid]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];
NSArray *pics = [member valueForKeyPath:#"photos"];
NSString *picCount = [NSString stringWithFormat:#"%d", [pics count]];
[photosCount setTitle:picCount forState:UIControlStateNormal];
NSLog(#"PHOTO: %#", picCount);
NSLog(#"FB: %#", fbid);
}
I tried putting NSURL request and connection code in viewDidLoad, but then I don't get anything back.
My NSURLConnection methods:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
data = [[NSMutableData alloc]init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
{
[data appendData:theData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
member = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *errorView = [[UIAlertView alloc]initWithTitle:#"Error" message:#"The download could not complete. Please make sure you are connected to internet" delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[errorView show];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
The populateUserDetails method that you have seen above:
- (void)populateUserDetails
{
if (FBSession.activeSession.isOpen) {
[[FBRequest requestForMe] startWithCompletionHandler:
^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error) {
if (!error) {
self.userProfileImage.profileID = user.id;
self.navigationItem.title = user.name;
self.fbid = user.id;
}
}];
}
}
This method basically sets the FBID once user is logged in. Other important things you should know that could help you understand my project:
FBID is set as NSString property in my .H file
All facebook connect thing goes on in AppDelegate
I need to dynamically set the NSURL after I find out who the user is.
if I manually input FBID in NSURL, then it works.
everything should be executed when user logins, I think that the timing of retrieving fbid and receiving data from web service is wrong but I can't get to fix it.
IF you need anything else, I will elaborate more and post more code if needed. -
PLEASE HELP as I've been looking for answers for last 3 days.
Your problem is that the populateUserDetails is called and it returns without waiting the code to be executed (because it's an async task with a completition handler, and when you call the NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://atnightcorp.com/api/member/id/%#/format/json", fbid]]; for the first time, the fbid is nuil or not set properly (also you should use self.fbid not fbid since fbid is a property).
So you should try to move the whole code that is handling the request from viewDidAppear into a separate method and you should call that method from startWithCompletionHandler after you set the line with self.fbid = user.id
Also call [super viewDidAppear:animated]; not with NO param (this won't solve your problem but this is the right way to do it)

How can i implement GTMOAuth in iOS

i need to implement GTMOAuth to my iOS app..for this i have downloaded the OAuth Library and write some code
- (GTMOAuthAuthentication *)myCustomAuth {
NSString *myConsumerKey = #"f964039f2d7bc82054"; // pre-registered with service
NSString *myConsumerSecret = #"c9a749c0f1e30c9246a3be7b2586434f"; // pre-assigned by service
GTMOAuthAuthentication *auth;
auth = [[[GTMOAuthAuthentication alloc] initWithSignatureMethod:kGTMOAuthSignatureMethodHMAC_SHA1
consumerKey:myConsumerKey
privateKey:myConsumerSecret] autorelease];
// setting the service name lets us inspect the auth object later to know
// what service it is for
auth.serviceProvider = #"Custom Auth Service";
return auth;
}
- (void)signInToCustomService {
NSURL *requestURL = [NSURL URLWithString:#"http://alpha.easyreceipts.com/api/v1/oauth/request_token"];
NSURL *accessURL = [NSURL URLWithString:#"http://alpha.easyreceipts.com/api/v1/oauth/access_token"];
NSURL *authorizeURL = [NSURL URLWithString:#"http://alpha.easyreceipts.com/api/v1/oauth/authorize"];
NSString *scope = #"http://alpha.easyreceipts.com/api/v1/";
GTMOAuthAuthentication *auth = [self myCustomAuth];
// set the callback URL to which the site should redirect, and for which
// the OAuth controller should look to determine when sign-in has
// finished or been canceled
//
// This URL does not need to be for an actual web page
[auth setCallback:#"http://alpha.easyreceipts.com/api/v1/"];
// Display the autentication view
GTMOAuthViewControllerTouch *viewController;
viewController = [[[GTMOAuthViewControllerTouch alloc] initWithScope:scope
language:nil
requestTokenURL:requestURL
authorizeTokenURL:authorizeURL
accessTokenURL:accessURL
authentication:auth
appServiceName:#"My App: Custom Service"
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)] autorelease];
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"myID"];
[[self navigationController] pushViewController:viewController
animated:YES];
}
but further i don't know how to implement it.Please help me Thanks in advance
First, if you want to implement an OAuth authentication system with the GTM library, you should get this one.
You can find the complete sources of GTMOAuth here.
Secondly, you can find an example of how to use it in the git repository too. You should find everything you need to at least authenticate.
If you have any more problem, please provide more informations.

How To Use OAuth in My iPhone App?

I'm trying to configure OAuth into my iPhone app to connect to another web service but I'm having problems with it.
I have downloaded the google gtm-oauth files and added them to my project.
- (GTMOAuthAuthentication *)myCustomAuth {
NSString *myConsumerKey = #"2342343242"; // pre-registered with service
NSString *myConsumerSecret = #"324234234242"; // pre-assigned by service
GTMOAuthAuthentication *auth;
auth = [[[GTMOAuthAuthentication alloc] initWithSignatureMethod:kGTMOAuthSignatureMethodHMAC_SHA1
consumerKey:myConsumerKey
privateKey:myConsumerSecret] autorelease];
// setting the service name lets us inspect the auth object later to know
// what service it is for
auth.serviceProvider = #"RunKeeper";
return auth;
}
- (void)signInToCustomService {
NSURL *requestURL = [NSURL URLWithString:#"https://runkeeper.com/apps/token"];
NSURL *accessURL = [NSURL URLWithString:#"https://runkeeper.com/apps/token"];
NSURL *authorizeURL = [NSURL URLWithString:#"https://runkeeper.com/apps/authorize"];
NSString *scope = #"http://example.com/scope";
GTMOAuthAuthentication *auth = [self myCustomAuth];
// set the callback URL to which the site should redirect, and for which
// the OAuth controller should look to determine when sign-in has
// finished or been canceled
//
// This URL does not need to be for an actual web page
[auth setCallback:#"http://www.example.com/OAuthCallback"];
// Display the autentication view
GTMOAuthViewControllerTouch *viewController;
viewController = [[[GTMOAuthViewControllerTouch alloc] initWithScope:scope
language:nil
requestTokenURL:requestURL
authorizeTokenURL:authorizeURL
accessTokenURL:accessURL
authentication:auth
appServiceName:#"RunKeeper"
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)] autorelease];
[[self navigationController] pushViewController:viewController
animated:YES];
}
This is the API I am trying to connect with: http://developer.runkeeper.com/healthgraph/registration-authorization
First, read up on oauth. it's a bit different from your normal login/pw type auth.
Second, you can use Google's oauth library at http://code.google.com/p/gtm-oauth/

Using tweet search with Twitter+OAuth on iPhone

Originally I needed the ability to use the search API with twitter. I did this using Matt Gemmell's great MGTwitterEngine. That code was very very simple and looked something like this:
- (void)viewDidLoad {
[super viewDidLoad];
tweetArrays = nil;
tweetNameArray = nil;
NSString *username = #"<username>";
NSString *password = #"<password>";
NSString *consumerKey = #"<consumerKey>";
NSString *consumerSecret = #"<consumerSecret>";
// Most API calls require a name and password to be set...
if (! username || ! password || !consumerKey || !consumerSecret) {
NSLog(#"You forgot to specify your username/password/key/secret in AppController.m, things might not work!");
NSLog(#"And if things are mysteriously working without the username/password, it's because NSURLConnection is using a session cookie from another connection.");
}
// Create a TwitterEngine and set our login details.
twitterEngine = [[MGTwitterEngine alloc] initWithDelegate:self];
[twitterEngine setUsesSecureConnection:NO];
[twitterEngine setConsumerKey:consumerKey secret:consumerSecret];
// This has been undepreciated for the purposes of dealing with Lists.
// At present the list API calls require you to specify a user that owns the list.
[twitterEngine setUsername:username];
[twitterEngine getSearchResultsForQuery:#"#HelloWorld" sinceID:0 startingAtPage:1 count:100];
}
This would end up calling the function:
- (void)searchResultsReceived:(NSArray *)searchResults forRequest:(NSString *)connectionIdentifier
And then I could do what I wanted with the searchResults. This required me to include the yajl library.
I then wanted to expand my code to allow users to tweet. I downloaded Ben Gottlieb's great code Twitter-OAuth-iPhone
So there's only one problem. The getSearchResultsForQuery returns a requestFailed with the following error:
Error Domain=HTTP Code=400 "The operation couldn’t be completed. (HTTP error 400.)"
To call this code I simply took the demo project in Twitter-OAuth-iPhone and added a call to getSearchResultsForQuery as seen here:
- (void) viewDidAppear: (BOOL)animated {
if (_engine) return;
_engine = [[SA_OAuthTwitterEngine alloc] initOAuthWithDelegate: self];
_engine.consumerKey = kOAuthConsumerKey;
_engine.consumerSecret = kOAuthConsumerSecret;
UIViewController *controller = [SA_OAuthTwitterController controllerToEnterCredentialsWithTwitterEngine: _engine delegate: self];
if (controller)
[self presentModalViewController: controller animated: YES];
else {
[_engine getSearchResultsForQuery:#"HelloWorld"];
// [_engine sendUpdate: [NSString stringWithFormat: #"Already Updated. %#", [NSDate date]]];
}
}
This as stated above returns a 400 error. Every other twitter API call I add here does work such as:
- (NSString *)getRepliesStartingAtPage:(int)pageNum;
Am I doing anything wrong? Or does getSearchResultsForQuery no longer work? The two code bases seem to use different versions of MGTwitterEngine, could that be causing the problem?
Thanks!
The problem is that you have to instantiate the twitter engine as an instance of SA_OAuthTwitterEngine, instead of MGTwitterEngine. When you call getSearchResultsForQuery, it uses a call to _sendRequestWithMethod to actually send the search request. SA_OAuthTwitterEngine overrides the MGTwitterEngine's implementation, but its version of that function doesn't work with getSearchResultsForQuery.
So, you need to go to getSearchResultsForQuery and make it use the MGTwitterEngine's version of the function.

Using Google Reader API and OAuth in iPhone app

I want to make an app that uses on the Google Reader API. But I'm finding out that there isn't an offical API for it - is there a problem using the unofficial API, in terms of App Store guidelines/approval? Would other apps (Reeder, etc) use this?
Also what is the best method for logging in? Is OAuth the preffered method? Is using Janrain a good idea?
Frankly Apple doesn't care if you use Google's unofficial API.
I worked for a customer on a RSS reader app that used Google Reader for syncing. We didn't use OAuth but the standard HTTP login which returns you a cookie where you'll have to extract a token from to use in consecutive calls to the various reader URLs.
I can post you the login code from my (old) proof of concept app.
It uses ASIHTTP and some custom string categories. The idea is to send a login request, get the response and extract the session ID/auth code from the response's cookie header. Then you can use that session ID/auth code for consecutive calls.
#pragma mark -
#pragma mark login
//this is your sessionID token you get from the login
//use this in consecutive calls to google reader
//this method returns you the header string you have to add to your request
//[request addRequestHeader: #"Cookie" value: [self sidHeader]];
- (NSString *) sidHeader
{
return [NSString stringWithFormat: #"SID=%#", [self sid]];
}
- (NSString *) authHeader
{
return [NSString stringWithFormat: #"GoogleLogin auth=%#",[self auth]];
}
//login to your google account and get the session ID
- (void) login
{
NSString *username = #"my.googlelogin#gmail.com";
NSString *password = #"mypassword123";
NSString *loginUrl = #"https://www.google.com/accounts/ClientLogin?client=NNW-Mac";
NSString *source = #"NNW-Mac"; //let's fake NetNewsWire
NSString *continueUrl = #"http://www.google.com";
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString: loginUrl]]; // log in & get cookies
[request addRequestHeader: #"User-Agent" value: #"NetNewsWire/3.2b25 (Mac OS X; http://www.newsgator.com/Individuals/NetNewsWire/)"];
[request setPostValue: username forKey: #"Email"];
[request setPostValue: password forKey: #"Passwd"];
[request setPostValue: #"reader" forKey: #"service"];
[request setPostValue: source forKey: #"source"];
[request setPostValue: continueUrl forKey: #"continue"];
[request setDelegate: self];
[request setDidFailSelector: #selector(loginRequestFailed:)];
[request setDidFinishSelector: #selector(loginRequestFinished:)];
[request start];
}
-(void)loginRequestFinished:(ASIHTTPRequest *)request
{
NSString *responseString = [request responseString];
//login failed
if ([responseString containsString: #"Error=BadAuthentication" ignoringCase: YES])
{
[self setLastError: [self errorWithDescription: #"Bad Username/Passsword" code: 0x001 andErrorLevel: 0x00]];
if ([delegate respondsToSelector: #selector(gReaderLoginDidFail:)])
{
[delegate gReaderLoginDidFail: self];
}
return NO;
}
//captcha required
if ([responseString containsString: #"CaptchaRequired" ignoringCase: YES])
{
[self setLastError: [self errorWithDescription: #"Captcha Required" code: 0x001 andErrorLevel: 0x00]];
if ([delegate respondsToSelector: #selector(gReaderLoginDidFail:)])
{
[delegate gReaderLoginDidFail: self];
}
return NO;
}
//extract SID + auth
NSArray *respArray = [responseString componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]];
NSString *sidString = [respArray objectAtIndex: 0];
sidString = [sidString stringByReplacingOccurrencesOfString: #"SID=" withString: #""];
[self setSid: sidString];
NSString *authString = [respArray objectAtIndex: 2];
authString = [authString stringByReplacingOccurrencesOfString: #"Auth=" withString: #""];
[self setAuth: authString];
//mesage delegate of success
if ([delegate respondsToSelector: #selector(gReaderLoginDidSucceed:)])
{
[delegate gReaderLoginDidSucceed: self];
}
return YES;
}
- (void)loginRequestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
//NSLog(#"login request failed with error: %#", [error localizedDescription]);
[self setLastError: error];
if ([delegate respondsToSelector: #selector(gReaderLoginDidFail:)])
{
[delegate gReaderLoginDidFail: self];
}
}
After login you can use sid and auth to forge requests to the Reader's API endpoints.
Example:
- (ASIHTTPRequest *) requestForAPIEndpoint: (NSString *) apiEndpoint
{
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString: apiEndpoint]];
[request addRequestHeader: #"User-Agent" value: #"NetNewsWire/3.2b25 (Mac OS X; http://www.newsgator.com/Individuals/NetNewsWire/)"];
[request addRequestHeader: #"Cookie" value: [self sidHeader]];
[request addRequestHeader: #"Authorization" value: [self authHeader]];
return request;
}
An interesting read about Google Reader and its private API is http://timbroder.com/2007/08/google-reader-api-functions.html
Please make sure to read the latest comments :)
/edit: I updated the code to use the auth header (which google introduced in june this year). I guess this would be the place to put your OAuth token in if you would use OAuth. guess
Ive since found this: "The Google Data APIs Objective-C Client Library provides an iPhone static library, a Mac OS X framework, and source code that make it easy to access data through Google Data APIs. " code.google.com/p/gdata-objectivec-client - which is great!
It doesn't include the Reader API however (because it's not been released).
I have been able to access the API by changing (in the OAuthSampleTouch example)
NSString *scope = #"http://www.google.com/m8/feeds/";
in OAuthSampleRootViewControllerTouch.m to
NSString *scope = #"http://www.google.com/reader/api/*";
and
urlStr = #"http://www.google.com/m8/feeds/contacts/default/thin";
to
urlStr = #"http://www.google.com/reader/atom/user/-/label/Design";
where Design is a folder name - check this http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI its a great help.
Update
I have since found that this technique to be the best / lightest / less-complicated :
Native Google Reader iPhone Application