Apple

Create your own web service for an iOS app, part four

This installment in our iOS development series explores handling multiple users, including storing gamers' credentials.

 

apple_620x465.png

This series shows how to create the web service backend for an app I created called iGlobe. Here's what we've accomplished so far:

We have the basic structure of the game, but we want to be able to handle multiple users. Each user will have to authenticate in order to play. We also want to be able to store the user's credentials so he or she doesn't have to enter them each time. NSUserDefaults is a pretty simple concept: An iOS app basically stores certain data locally.

We'll start by going to our ModalVIewController Class and adding properties and methods. Then we'll work on some php code for our backend.

First, import the Santiapps Helper class created above into our .h file. While you're there, add some protocols, properties and methods.

 

#import <UIKit/UIKit.h>
#import "SantiappsHelper.h"

@interface ModalViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate>{
	
}
@property (nonatomic,retain) NSURLConnection *myConnection;
@property (nonatomic,retain) NSMutableData *incomingPostData;
@property (nonatomic,retain) IBOutlet UIButton *registerUser;
@property (nonatomic,retain) IBOutlet UIButton *loginUser;
@property (nonatomic,retain) IBOutlet UIButton *dismissModal;
@property (nonatomic,retain) IBOutlet UITextField *userName;
@property (nonatomic,retain) IBOutlet UITextField *userPass;
@property (nonatomic,retain) IBOutlet UITextField *userEmail;

-(IBAction)userWillDismiss;
-(IBAction)userWillLogIn;
-(IBAction)userWillRegister;
-(NSDictionary*)addUser:(NSString *)usuario withPass:(NSString *)clave withAddress:(NSString*)direccion;
-(NSString*)checkUserLogin:(NSString *)loginUsuario withPass:(NSString *)loginClave;
@end
 

The navigation controller delegate is required, along with the UIImagePickerControllerDelegate for picking images. We'll use them when the user uploads his or her picture.

As for properties, we'll revisit our old friend, NSURLConnection, for which we require NSMutableData. Next, we'll connect the buttons and textfields to their controls.

The methods will be used for when the user dismisses the login screen. It's always important to add a way out of a login screen, even if that means the user will have limited access to the app. The login method will do just that, whereas the register method will create a new user. We'll use the other methods for checking a login request. Let's see what these look like.

 

#import "ModalViewController.h"

@implementation ModalViewController

-(IBAction)userWillLogIn{
	//Call checklogin.php to compare user/pass and return value
	NSString *loginSuccess = [self checkUserLogin:self.userName.text withPass:self.userPass.text];
	if ([loginSuccess isEqualToString:@"SUCCESS"]) {
		NSLog(@"We did it...");
		//If all is well, then store userDefaults
		NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
		[prefs setObject:self.userName.text forKey:@"storedUser"];
		[prefs setObject:self.userPass.text forKey:@"storedPass"];
		[prefs synchronize];
		[self dismissViewControllerAnimated:YES completion:nil];
	} else {
		NSLog(@"Authentication Failed..");
		//Add UIAlertView
		UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Oops..." message:loginSuccess delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
		[alertView show];
		[alertView release];
	}
	//if checklogin.php = OK then dismiss
	
}

-(IBAction)userWillRegister{
	// Create uniqueIdentifier
	//UIDevice *device = [UIDevice currentDevice];
	//NSString *uniqueIdentifier = [device uniqueIdentifier];
	
	//1. Get the LOGIN errorsDictionary
	NSDictionary *errorsDict = [self addUser:self.userName.text withPass:self.userPass.text withAddress:self.userEmail.text];
	//2. if dictcount = 1 then dismiss && = 5926, then OK
	if ([[errorsDict objectForKey:@"code1"] intValue] == 5926) {
		NSLog(@"yeay");
		//If all is well, then store userDefaults
		NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
		[prefs setObject:self.userName.text forKey:@"storedUser"];
		[prefs setObject:self.userPass.text forKey:@"storedPass"];
		[prefs synchronize];
		//Dismis ModalVC after users first login
		[self dismissViewControllerAnimated:YES completion:nil];
	} else {
		NSLog(@"Errors");
		// extract nsdict
		NSMutableString *resultString = [NSMutableString string];
		for (NSString* key in [errorsDict allKeys]){
			if ([resultString length]>0)
				[resultString appendString:@"&"];
			[resultString appendFormat:@"%@=%@", key, [errorsDict objectForKey:key]];
		}
		NSLog(@"rS:%@",resultString);
		//Add UIAlertView
		UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Check your user,pass or email" message:resultString delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
		[alertView show];
		[alertView release];
		}
}

-(IBAction)userWillDismiss{
    [self dismissViewControllerAnimated:YES completion:nil];
}

//registering user----moved to modalVC
-(NSString*)checkUserLogin:(NSString *)loginUsuario withPass:(NSString *)loginClave{
	//CREATE URL TO SEND
	NSString *urlString = [NSString stringWithFormat:@"username=%@&password=%@",loginUsuario,loginClave];
	NSLog(@"login string:%@",urlString);
	//POST THE STRING
    NSData *postData = [urlString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
    NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];
    NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.yourserver.com/iGlobe/login2/checklogin.php"]];
    [request setURL:url];
    [request setHTTPMethod:@"POST"];
    [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPBody:postData];
    
    NSURLResponse *response = nil;
    NSError *error = nil;
	// We should probably be parsing the data returned by this call, for now just check the error.
    NSData *myData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
	//NSError *outError = NULL;
	//NSDictionary *tempDict = [NSDictionary dictionaryWithJSONData:myData error:&outError];
	NSString *string=[[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding ];
	//NSLog(@"string es:%@, %i, %C",string,[string length],[string characterAtIndex:7]);
	NSLog(@"string ess:%@",string);
	//NSLog(@"Dict of errors:%@",tempDict);
	return string;
}



-(NSDictionary*)addUser:(NSString *)usuario withPass:(NSString *)clave withAddress:(NSString*)direccion{
	//CREATE URL TO SEND
	NSString *urlString = [NSString stringWithFormat:@"username=%@&password=%@&email=%@",usuario,clave,direccion];
	NSLog(@"user registration string:%@",urlString);
	//POST THE STRING
    NSData *postData = [urlString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
    NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];
    NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.yourserver.com/iGlobe/login2/user_add_save.php"]];
    [request setURL:url];
    [request setHTTPMethod:@"POST"];
    [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPBody:postData];
    
    NSURLResponse *response = nil;
    NSError *error = nil;
	// We should probably be parsing the data returned by this call, for now just check the error.
    NSData *myData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
	NSError *outError = NULL;
	NSDictionary *tempDict = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:&outError];
                              
	NSLog(@"Dict of errors:%@",tempDict);
	return tempDict;
}

///////////////////////////////////----------------PHOTO UPLOAD-----
-(IBAction)uploadPhoto:(id)sender{
    NSLog(@"picker");
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;
    picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    [self presentViewController:picker animated:YES completion:nil];
}

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
    NSLog(@"pickerPicked");

    UIImage *profile_image = [info objectForKey:UIImagePickerControllerOriginalImage];
    
    [self uploadImage:UIImageJPEGRepresentation(profile_image, 1.0) filename:@"globe57.png"];
    [picker dismissViewControllerAnimated:YES completion:nil];
}

-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [picker dismissViewControllerAnimated:YES completion:nil];
}

- (BOOL)uploadImage:(NSData *)imageData filename:(NSString *)filename{
    
    NSLog(@"uploading");

    NSString *urlString = @"http://www.yourserver.com/iGlobe/photos/uploadPhoto.php";
    
    NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
    [request setURL:[NSURL URLWithString:urlString]];
    [request setHTTPMethod:@"POST"];
    
    NSString *boundary = @"0xKhTmLbOuNdArY";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
    [request addValue:contentType forHTTPHeaderField: @"Content-Type"];
    
    NSMutableData *body = [NSMutableData data];
    
    [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
    //Set the filename
    [body appendData:[[NSString stringWithString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"userfile\"; filename=\"%@\"\r\n",filename]] dataUsingEncoding:NSUTF8StringEncoding]];
    
    [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    //append the image data
    [body appendData:[NSData dataWithData:imageData]];
    
    [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
    [request setHTTPBody:body];
    
    NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    NSString *returnString = [[[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding] autorelease];
    
    NSLog(@"returningOKString");

    return ([returnString isEqualToString:@"OK"]);
}

//////////////////////////////

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc. that aren't in use.
}

- (void)viewDidUnload {
    [super viewDidUnload];
    
}


- (void)dealloc {
    [super dealloc];
}


@end
 

We'll review the code method by method.  

  1. userWillLogin makes a call to the web fetch method that sends the submitted data to the server and runs a check (checkUserLogin). If authentication was successful, we store the user data in NSUserDefaults so the user doesn't have to log in again. Otherwise, we display an alert view with the server error response.
  2. checkUserLogin makes the web fetch and returns the server response.
  3. userWillRegister calls to create a new user when the user taps on the register button. It calls the addUser:withPass:withAddress:.
  4. addUser:withPass:withAddress: makes the web post to add that user to our web database.
  5. userWillDismiss is self-explanatory.
  6. uploadPhoto is in charge of calling the UIImagePicker so the user can select his or her image to upload.
  7. imagePickerController:didFinishPickingMediaWithInfo returns the image selected by the user and passes it to uploadImage. For simplicity's sake, we are not using the picked image; instead, we hardcoded a placeholder image just to make things move along more smoothly.
  8. uploadImage:filename makes the web post to upload the image to the server.

Now we need the backend to respond to these two requests. Specifically, we need two php files (checklogin.php and user_add_save.php) and their helper files. Here is checklogin.php.

 

<?php

include"login_config.php";

//Connection String Variables_________________________________________________

   // connect to the server
   mysql_connect( $db_host, $db_username, $db_password )
      or die( "Error! Could not connect to database: " . mysql_error() );
   
   // select the database
   mysql_select_db( $db )
      or die( "Error! Could not select the database: " . mysql_error() );

//IBM suggested scrub for URL request
$urlun = strip_tags(substr($_REQUEST['username'],0,32));
$urlpw = strip_tags(substr($_REQUEST['password'],0,32));

$cleanpw = md5($urlpw);

//echo"Cleanpw: $cleanpw<br>";

//$sql="SELECT * FROM agents WHERE username='$urlun' and password='$urlpw'";
$sql="SELECT * FROM users WHERE username='$urlun' and password='$cleanpw'";

$result=mysql_query($sql);

// Mysql_num_row is counting table rows

$count=mysql_num_rows($result);

// If result matches $myusername and $mypassword, table row must be 1 row

//echo"Count:$count<br>";

if($count==1){

// Register $myusername and redirect to file designated success file

$cookie_name ="$cookiename";

$cookie_value ="$urlun";

//set to 24 hours

$cookie_expire ="86400";

setcookie($cookie_name,$cookie_value,time() + (86400),"/", $cookie_domain);

header("location:$successful_login_url");

}else{

header("location:$failed_login");

}


?> 
and user_add_save.php:

<?
include"master_inc.php";
//--------------------------------------------------------------------------RECEIVE LOCAL VARIABLES FROM FORM
$lastname = strip_tags(substr($_POST['lastname'],0,32));
$firstname = strip_tags(substr($_POST['firstname'],0,32));
$phone = strip_tags(substr($_POST['phone'],0,32));
$password_hint=$_REQUEST['password_hint'];
$noERROR=1;
$udidposted = 9;

//---------------------------------------------------------------------------CHECK IF USERNAME IS LONG ENOUGH
$username = strip_tags(substr($_POST['username'],0,32));
if(trim($username)!=='' && strlen(trim($username)) >= 4){
//---------------------------------------------------------------------------IF LONG ENOUGH THEN RUN A QUERY GETTING ALL DATA FROM THAT USER
$sql="SELECT * FROM users WHERE username='$username'";
$result=mysql_query($sql);
$count=mysql_num_rows($result);
//---------------------------------------------------------------------------IF $sql GOTTEN HAS ROW COUNT > 1 THEN USER ALREADY EXISTS----------------SET EXISTING USER 104 FLAG
if($count>0){
$username_already_in_use = 3141;
}
//---------------------------------------------------------------------------ELSE USERNAME IS TOO SHORT?!?!-------------------------------------------------------SET USER TOO SHORT 104 FLAG
}else{
$username_too_short = 3142;}

//---------------------------------------------------------------------------EMAIL FORMAT CHECK
$email_raw = $_REQUEST['email'];
if(eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@([a-z0-9-]{2,3})+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$", $email_raw))
{ 
$email = $email_raw;
}else{
//---------------------------------------------------------------------------IF INVALID EMAIL THEN----------------------------------------------------------------------SET INVALID EMAIL 104 FLAG
$bad_email=3143;
} 

//email unique?
$sql="SELECT * FROM users WHERE email='$email'";
$result=mysql_query($sql);
$count=mysql_num_rows($result);
if($count>0){
//---------------------------------------------------------------------------IF SQL FOR EMAIL RETURNS A ROW THEN------------------------------------------------SET EMAIL 104 FLAG
$email_already_in_use=3144;
}

//Secure Password Format Checks
$pw_clean = strip_tags(substr($_POST['password'],0,32));
if (preg_match("/^.*(?=.{4,})(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).*$/", $pw_clean, $matches)) {
}else{
//---------------------------------------------------------------------------IF PW NOT IN FORMAT THEN-----------------------------------------------------------------SET PW 104 FLAG
$pw_insecure = 3145;
}

//---------------------------------------------------------------------------IF ERROR FLAGS ARE SET THEN LOG HEADERS----------------------------
if($username_already_in_use==3141 OR $email_already_in_use==3144 OR $pw_insecure==3145 OR $bad_email==3143 OR $username_too_short==3142){
header(
"location:user_add_errors.php?pw_insecure=$pw_insecure&email_already_in_use=$email_already_in_use&username_already_in_use=$username_already_in_use&bad_email=$bad_email&username_too_short=$username_too_short"); 
die();
}
else {header("location:user_add_errors.php?noERROR=$noERROR");}

//End Error Checks________________________

//-------------------------------------------------------------------INSERT INTO SQL
//Encrypt Password
$encrypted_pw = md5($pw_clean);
$query = "INSERT INTO `users` (`username`,
`password`,
`lastname`,
`firstname`,
`email`,
`phone`,
`password_hint`,
`udid`,
`userCreated`)
VALUES
(
'$username',
'$encrypted_pw',
'$lastname',
'$firstname',
'$email',
'$phone',
'$password_hint',
'$udidposted',
now())"; 
// save the info to the database
$results = mysql_query( $query );
// print out the results
if( $results )
{
	if($username_too_short==3142){echo"ShortUser=".$username_too_short;}
	
	if($username_already_in_use==3141){echo"UserTaken=".$username_already_in_use;}

	if($email_already_in_use==3144){echo"EmailTaken=".$email_already_in_use;}

	if($pw_insecure==3145){echo"ShortPass=".$pw_insecure;}
	
    if($bad_email==3143){echo"BadEmail".$bad_email;}
	

	
//echo( "<font size='2' face='Verdana, Arial, Helvetica, sans-serif'>Your changes have been made sucessfully. <br><br><a href='login.php'>Back to login</a></font> " );
}
else
{
die( "Trouble saving information to the database: " . mysql_error() );
}
//--------------------------------------
$sql="SELECT * FROM users";
$result=mysql_query($sql);
$count=mysql_num_rows($result);
if($count==1){

$query = "UPDATE `users` SET `permissions`='5' WHERE `email`='$email'"; 
//---------------------------------------SAVE the info to the database
$results = mysql_query( $query );
//---------------------------------------JUST PRINT CODE
if( $results )
{ echo( "ADMINCREATED" );
}
else
{
die( "ERRORSAVINGADMIN" . mysql_error() );
}

}
?>
 

We won't review all of what these files do, but we have made them available to download. Basically, checklogin queries the database for the user and password submitted; user_add_save.php verifies the user and password submitted for registration, as well as the email, and then returns a result accordingly. 

You must configure your database, user, and password accordingly in the login_config.php file. Another important php file is user_add_errors.php, which takes care of errors during registration such as a username that's too short, a password that's too simple, an invalid email, or a user that already exists.

In part five of our series, we'll start putting all of the pieces together. See you next time!

 

About

My name is Marcio Valenzuela and I am from Honduras. I have been coding in iOS for 5 years and I previously worked on web applications in ASP and PHP/mySQL. I have a few apps up in the app store and am currently working on a couple of Cocos2d games...

2 comments
thekruser
thekruser

Also, just out of curiosity, why are you setting vars for error codes instead of just creating an array of error codes?

Editor's Picks