How to implement login functionality to native iphone app - iphone

I'm working on an app that asks the user to login to access some of his infrmations. I have a login.php file that is store on the server and all the usernames and passwords on the database.
On my app i have 2 uitextfields one for the username and one for the password. I understand that i can use the POST method to pass on the input to the web server and check wether the username and password match to then load the rest of the data. This is where am having trouble, can anyone help me with it, how do i pass on the inputs from the uitextfield to that web service to run the long.php script?

Skram's answer is correct, although you might be thinking what is ASIHTTPRequest?
You can get the framework here:
ASIHTTPrequest home
Here is a short beautiful tutorial on how to use it:
Awesome Tutorial
And here is some code I used to do a login some time ago:
-(IBAction)doLogin{
//make sure you have text in the username field, this is optional
if(![[username text] isEqualToString:#""]){
//URL of your web service
NSURL *url = [NSURL URLWithString:#"http://localhost:8888/myservice.php"];
//instantiate request object with URL
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
//Set post values before you send it off (forcing password to lowercase)
[request setPostValue:[[self.password text] lowercaseString] forKey:#"password"];
[request setPostValue:[username text] forKey:#"email"];
//this is optional, I have switch controlling what methods are called in the web service
[request setPostValue:#"2" forKey:#"method"];
//set the delegate to self for ASIHTTPRequest delegates (things you'll see in the tutorial)
[request setDelegate:self];
//send out request
[request startSynchronous];
//Now this code handles what happens after the web service call, notice I use a dictionary called user info to hold the response and I check the string to verify pass or fail and act accordingly.
NSString *verify = [NSString stringWithFormat:#"%#",[userinfo objectForKey:#"verify"]];
if([verify isEqualToString:#"pass"]){
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:
#"MainStoryboard_iPhone" bundle:[NSBundle mainBundle]];
UITabBarController *mainMenu =
[storyboard instantiateViewControllerWithIdentifier:#"mainMenu"];
[self.navigationController pushViewController:mainMenu animated:YES];
}
else{
//show login failed Dialog or something...
}
}}
Here some more code, this is what happens when the server returns a response.
- (void)requestFinished:(ASIHTTPRequest *)request{
if (request.responseStatusCode == 400) {
NSLog(#"Something is wrong");
} else if (request.responseStatusCode == 403) {
NSLog(#"Something is wrong");
} else if (request.responseStatusCode == 200) {
//the response code is good so proceed.
NSString *responseString = [request responseString];
userinfo = [responseString JSONValue];
NSLog(#"%#", [userinfo objectForKey:#"id"]);
NSLog(#"%#", [userinfo objectForKey:#"user"]);
NSLog(#"%#", [userinfo objectForKey:#"verify"]);
} else {
//print mystery code.
NSLog(#"%d",request.responseStatusCode);
}
}
Basically when you start the request using request startSynchronous it executes you server side code and returns a response string (or fails), you catch and handle the response string (or failure)in a delegate method you must implement from ASIHttpRequest -(void)requestFinished:(ASIHTTPRequest *)request. In the sample code I am parsing the response string into a JSON and then putting it into a dictionary for later use.
If you go through the tutorial this will all make sense to you very quickly. Hope it helps, good luck!

You would need to use a POST Request made to login.php Script on your server with a username and password value.
You can achieve this with NSURLConnection or a Framework such as ASIHTTPRequest's ASIFormDataRequest.
On your server you should do something like $_POST['username'] and $_POST['password'] to retrieve the values sent to your script to do the processing for your database.
EDIT: Basic example from iPhone Side with ASIHTTPRequest.
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:url]];
[request setPostValue:#"some_user" forKey:#"username"];
[request setPostValue:#"some_password" forKey:#"password"];
[request setDelegate:self];
[request startAsynchronous];
Implement the following method to catch the response:
-(void)requestFinished:(ASIHTTPRequest *)request {
NSLog(#"%#", [request responseString]);
}

Related

Implementation of Async Request causing many leaks

I've inherited a project that uses of ASIHttpRequest for all network communication. I am unclear as to which specific version we're using. All I can tell is that, from the .h files, the oldest creation date on a particular file is 17/08/10 (ASIDataDecompressor).
We're using completion and failure blocks. For some reason, the failure block is often triggered, which should only really happen if the server fails to respond. Our logs look sane, and we haven't received any notifications (Airbrake) that there were server problems around the time the errors occur, so for now I'm moving forward with the assumption that our server is fine and it's the app that is the culprit.
I decided to run the app through Instruments (Leaks) and was astonished to see that when I force a request to fail, ~27 leaks are created immediately. I'm don't know how to get around Instruments all that well, so I'm not really sure what to do with the information now that I have it.
I figured I'd post my code to see if there's anything glaring.
In viewDidLoad, this code is executed
[[MyAPI sharedAPI] getAllHighlights:pageNumber:perPage onSuccess:^(NSString *receivedString,NSString *responseCode) {
[self getResults:receivedString];
if(![responseCode isEqualToString:#"Success"]) {
[self hideProgressView];
appDelegate.isDiscover_RefreshTime=YES;
[[MyAPI sharedAPI] showAlert:responseCode];
} else {
NSString *strLogEvent=#"Discover_Highlights_Loaded Page_";
strLogEvent=[strLogEvent stringByAppendingFormat:#"%i",intPageNumber];
[FlurryAnalytics logEvent:strLogEvent timed:YES];
}
} onFail:^(ASIFormDataRequest *request) {
NSDictionary *parameters = [[MyAPI sharedAPI] prepareFailedRequestData:request file:#"Discover" method:_cmd];
[FlurryAnalytics logEvent:#"Unable_to_Connect_to_Server" withParameters:parameters timed:true];
[self hideProgressView];
appDelegate.isDiscover_RefreshTime=YES;
[[AfarAPI sharedAPI] showAlert:#"Unable to Connect to Server."];
[tblHighlightsGrid reloadData];
[tblListHighlights reloadData];
}];
These typedefs have been defined at the top of API Singleton:
typedef void (^ASIBasicBlockWrapper)(NSString *responseString,NSString *responseCode);
typedef void (^ASIBasicBlockWrapperFail)(ASIFormDataRequest *request);
MyAPISingleton#getAllHighlights...
- (void)getAllHighlights:(NSString *)pageNumber:(NSString *)perPage onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2{
NSString *access_token= [[NSUserDefaults standardUserDefaults] objectForKey:#"access_token"];
NSString *url = [baseURL stringByAppendingFormat:AFAR_GET_ALL_HIGHLIGHTS_ENDPOINT, pageNumber,perPage];
if (access_token) { url = [url stringByAppendingFormat:ACCESS_TOKEN, access_token]; }
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:url]];
[request setRequestMethod:#"GET"];
[request setDelegate:self];
[self executeAsynchronousRequest:request onSuccess:cb1 onFail:cb2];
}
And finally, MyAPI#executeAsynchronousRequest:
- (void) executeAsynchronousRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
[request setCompletionBlock:^{
int statusCode = [request responseStatusCode];
NSString *statusMessage = [self statusErrorMessage:statusCode];
cb1([request responseString],statusMessage);
}];
[request setFailedBlock:^{
cb2(request);
}];
[request startAsynchronous];
}
Does anything stand out as to why 27 leaks are created?
I figured this out.
The ASIHttpRequest Documentation is very clear about the fact that you need to designate your request object with the __block storage mechanism:
Note the use of the __block qualifier when we declare the request, this is important! It tells the block not to retain the request, which is important in preventing a retain-cycle, since the request will always retain the block.
In getAllHighlights(), I'm doing that, but then I'm sending my request object as an argument to another method (executeAsyncRequest). The __block storage type can only be declared on local variables, so in the method signature, request is just typed to a normal ASIFormDataRequest, and so it seems as though it loses its __block status.
The trick is to cast (I'm not sure if that's technically accurate) the argument before using it in a block.
Here's my leak free implementation of executeAsyncRequest:
- (void) executeAsyncRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
// this is the important part. now we just need to make sure
// to use blockSafeRequest _inside_ our blocks
__block ASIFormDataRequest *blockSafeRequest = request;
[request setCompletionBlock: ^{
int statusCode = [blockSafeRequest responseStatusCode];
NSString *statusMessage = [self statusErrorMessage:statusCode];
cb1([blockSafeRequest responseString],statusMessage);
}];
[request setFailedBlock: ^{
cb2(blockSafeRequest);
}];
[request startAsynchronous];
}

Logic Issue - NSObject class - Beginner

I have 2 questions.
1.) I am creating a NSObject class, and i am having the following code in it. (ASIHTTPRequest POST).
The name of the NSObject class is called, SendToServer. I call the class as follows;
SendToServer *sv = [[SendToServer alloc]];
sv.grabURLInTheBackground ;
NSLog(#"This line is executed ");
The following is the code that is in the SendToServer NSObject class.
- (void)grabURLInTheBackground
{
if (![self queue]) {
[self setQueue:[[[NSOperationQueue alloc] init] autorelease]];
}
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
[[self queue] addOperation:request]; //queue is an NSOperationQueue
}
- (void)requestDone:(ASIHTTPRequest *)request
{
NSString *response = [request responseString];
}
- (void)requestWentWrong:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
The problem is that, the code executes the line sv.grabURLInTheBackground ; and before it executes the requestDone or requestWentWrong methods, it executes the NSLog (NSLog(#"This line is executed "); )
What i want my program to do is to complete all the operations in the SendToServer NSObject class and then Execute the NSLog (In a sequence).
First execute sv.grabURLInTheBackground ; once all the activities in that method/class is over, then return to the code and execute the other line which is NSLog(#"This line is executed "); .
2.) I need to return a String when the requestDone method is executed. How do i modify the code to do so;
- (NSString * )requestDone:(ASIHTTPRequest *)request {
}
but how do i edit [request setDidFinishSelector:#selector(requestDone:)];, for the above code ?
------------------------------------------------------------------------------------------------------------------------------------------
EDIT
I am doing this for user login. Upon button click i will be calling the grabURLInTheBackground method from the NSObject class. And the viewcontroller needs to know if the user login was successful or failed.
SendToServer *sv = [[SendToServer alloc]];
[sv grabURLInTheBackground] ;
NSLog(#"User login SUcess or failed %#", [sv userloginSucessOrFail]);
For example say [sv userloginSucessOrFail] returns if the user login was success or failed.
What hapence here, is that after [sv grabURLInTheBackground] is called, it directly goes and executes the NSLog(#"User login SUcess or failed %#", [sv userloginSucessOrFail]); line of code.
What i want is, i need to find a way to let my ViewCOntroller know if the user login was a Success or failure.
First: call init on your object.
Second: grabURLInTheBackground is a method not a property. It should by called with square brackets
So you code becomes:
SendToServer *sv = [[SendToServer alloc] init];
[sv grabURLInTheBackground];
NSLog(#"This line is executed ");
To accomplish point 1) you need to make a synchronous request
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *response = [request responseString];
}
The problem is that if this code is executed on the main thread is blocking (not good)...
For the second point. You can't.
EDIT:
What you have to do is something like the following steps:
Before calling grabURLInTheBackground you have to notify the user that a request is pending.. like putting an UIActivityIndicator, or disabling the UI,...
when you receive the callback then update the UI: hide the activity indicator, re-enable the UI... or if the request failed, notify the user.

Delaying, cancelling or replacing a ASIFormDataRequest in a NSOperationQueue (for a searchbox)

I'm successfully making a ASIFormDataRequest using the below code.
//get groups
if (![self queue]) {
[self setQueue:[[[NSOperationQueue alloc] init] autorelease]];
}
//make the url by appending the URL from the Constant class to the jsp name
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#", URL, #"connectors/searchGroupsServlet.jsp"]];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request addRequestHeader:#"User-Agent" value:USER_AGENT];
[request addPostValue:[login username] forKey:#"username"];
[request addPostValue:[login password] forKey:#"password"];
[request addPostValue:[searchText lowercaseString] forKey:#"query"];
[request addPostValue:GROUP_FILTER_LIMIT forKey:#"limit"];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
This request is currently made on every key press a user makes in a searchbox (The text typed is sent off in the request as the search string). However, rather than sending the request on every key press, I want to delay the request by a second to allow users to type further characters into the searchbox before the request is sent.
I've successfully made a thread that waits a second as users continue to type (although admittedly Im not convinced this is the best way to do it yet, but it works for now)...
this
[self performSelectorInBackground:#selector(wait:) withObject:request];
calls this
-(void)wait:(NSString *)request
{
[NSThread sleepForTimeInterval:1.00];
[[self queue] addOperation:request]; //queue is an NSOperationQueue
}
but, if a user continues to type, I haven't managed to work out how to cancel the request or not put the request in the queue, or empty the queue and replace it with the new request.
Finally, obviously I could force users to wait until they have pressed the 'search' button on the pop-up keyboard, but I was hoping to provide search results without that.
Thanks
The answer was to create an NSTimer, and invalidate it whenever a new key press had been made. Then start it again.
[timer invalidate];
You can try this to cancel
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument

ASIFormDataRequest not successful, when using iPhone SDK

I'm trying this:
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:
[NSURL URLWithString:my_url]];
[request setPostValue:id_ forKey:#"id_source"];
[request setPostValue:email forKey:#"email"];
[request startSynchronous];
And I got from console
wait_fences: failed to receive reply: 10004003
Which is something internal.
Do you have idea why? I'm trying the form for real, from browser, and it works with no problems. Don't have error in the URL, or in any parameter.
To debug the request, try getting the `response in this way:
NSError *error = [request error];
if (!error) {
NSString *response = [request responseString];
}
error should tell you everything you need to know, and should shield your response from any problems. Here's a good post on NSError, if you haven't done this before.
About the existing wait_fences thing... I think I've got this one figured out, based on some other sources listed below.
it looks like this issue comes about when an input field fails to resign its firstResponder status. My long shot guess is that the keyboard that's helping you populate the form you're processing isn't resigning its status as firstResponder.
so, in your view controller, assuming you've got a text field declared, you might try this:
- (void)viewDidLoad
{
// set up the text field
[self.textField setDelegate:self];
[self.textField addTarget:self
action:#selector(textFieldFinished:)
forControlEvents:UIControlEventEditingDidEndOnExit];
[super viewDidLoad];
}
- (IBAction)textFieldFinished:(id)sender
{
[sender resignFirstResponder];
}
Some posts I looked at to form this opinion:
"wait_fences: failed to receive reply: 10004003"?
http://www.iphonedevsdk.com/forum/iphone-sdk-development-advanced-discussion/17373-wait_fences-failed-receive-reply-10004003-a.html
http://discussions.apple.com/thread.jspa?threadID=2014220

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