Wikipedia

Search results

04 March 2014

OAuth primer for iOS

So tonight I had a nice discussion with a colleague about OAuth. We had a nice review on OAuth.. and the take home point (please correct below!) is that OAuth is a user authentication protocol. there is v1 and v2.. v2 differs in that you can make requests to the app with a token.

Nevertheless, you pass over the user authentication process over to the OAuth protocol, and leverage that the user can utilize their other account(s) at popular and common services.

So we proceeded to create a makeshift OAuth controller.

We will be accessing the user login with UIWebView, the endpoint for OAuth services generates HTML that allows the user to log in to the host's Users database. So let's declare our variables:

@interface LoginViewController () 
@property (strong, nonatomic) UIWebView* webView;
@property (strong, nonatomic) NSNumber* userId;
@end

On loadView (yes, no storyboard or xib), let's go ahead and make everything, the page is a webView that loads the authorization endpoint of Meetup, and a token and key are required for this to work. Therefore, I previously created my account, and generated an application utilizing their console.

- (void)loadView
{
    [super loadView];
 
    UIButton* exit = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    exit.backgroundColor = [UIColor clearColor];
    [exit setTitle:@"X" forState:UIControlStateNormal];
    [exit addTarget:self action:@selector(exit) forControlEvents:UIControlEventTouchUpInside];
    exit.frame = CGRectMake(20, 20, 44, 44);
    [self.view addSubview:exit];
    
    _webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, self.view.bounds.size.height, self.view.bounds.size.width, self.view.bounds.size.height)];
    _webView.delegate = self;
    [self.view addSubview:_webView];
}

(void) goMeetup is a delegate method I call in my other view controller:

- (void)goMeetup
{
    NSString* scope = [NSString stringWithFormat:@"https://secure.meetup.com/oauth2/authorize?client_id=%@&response_type=code&redirect_uri=%@", MEETUP_CLIENTID, @"http://www.filmproj.com"];
 
    CGRect newRect = self.view.bounds;
    
    [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:scope]]];
    
    [UIView animateWithDuration:0.3f animations:^{
        _webView.frame = newRect;
    }];
}

I'm utilizing a helper method to parse the response. I want the userId, and to make a request of that user with that value. Therefore, if the value exists, then I will request the details of that user:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSLog(@"request url lastpathComponent = %@", request.URL.lastPathComponent);
    NSString *html = [webView stringByEvaluatingJavaScriptFromString:
                      @"document.body.innerHTML"];
    NSString* part = [self scanString:html startTag:@"MM.Member={" endTag:@"\
"]; NSString* anId = [self scanString:part startTag:@"id:" endTag:@","]; NSNumberFormatter * f = [[NSNumberFormatter alloc] init]; [f setNumberStyle:NSNumberFormatterDecimalStyle]; self.userId = [f numberFromString:anId]; if (self.userId) { [self userApi]; } else { [self alertError]; } return YES; } - (NSString *)scanString:(NSString *)string startTag:(NSString *)startTag endTag:(NSString *)endTag { NSString* scanString = @""; if (string.length > 0) { NSScanner* scanner = [[NSScanner alloc] initWithString:string]; [scanner scanUpToString:startTag intoString:nil]; scanner.scanLocation += [startTag length]; [scanner scanUpToString:endTag intoString:&scanString]; } return scanString; } - (void)userApi { NSString* urlString = [NSString stringWithFormat:@"https://api.meetup.com/members?member_id=%@&key=%@", self.userId, MEETUP_KEY]; [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { if (data) { NSDictionary* dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; //NSLog(@"dictionary = %@", dictionary); if (dictionary) { NSDictionary* userResponse = [dictionary valueForKey:@"results"][0]; //NSLog(@"userResponse = %@", userResponse); if (userResponse) { NSString* city = [userResponse valueForKey:@"city"]; NSString* country = [userResponse valueForKey:@"country"]; NSString* name = [userResponse valueForKey:@"name"]; NSString* state = [userResponse valueForKey:@"state"]; NSString* avatarUrl = [userResponse valueForKey:@"photo_url"]; NSArray* topicsArray = [userResponse valueForKey:@"topics"]; NSMutableArray* topicsArrayHolder = [NSMutableArray array]; for (NSDictionary* json in topicsArray) { NSLog(@"topicsArray = %@", topicsArray); NSNumber* jsonId = [json valueForKey:@"id"]; NSString* jsonName = [json valueForKey:@"name"]; NSString* jsonUrlKey = [json valueForKey:@"urlKey"]; MeetupTopic* topic = [[MeetupTopic alloc] initWithTopicId:jsonId name:jsonName urlKey:jsonUrlKey]; [topicsArrayHolder addObject:topic]; } [[UserAuthenticated sharedInstance] makeUserWithId:self.userId username:name city:city state:state country:country imageUrl:avatarUrl andTopics:topicsArrayHolder]; [topicsArrayHolder removeAllObjects]; // tests for user authenticated object NSNumber* user0 = [[UserAuthenticated sharedInstance] userId]; NSString* user1 = [[UserAuthenticated sharedInstance] userName]; NSString* user2 = [[UserAuthenticated sharedInstance] city]; NSString* user3 = [[UserAuthenticated sharedInstance] state]; NSString* user4 = [[UserAuthenticated sharedInstance] country]; NSArray* user5 = [[UserAuthenticated sharedInstance] topicsArray]; UIImage* user6 = [[UserAuthenticated sharedInstance] avatar]; NSLog(@"%@\n%@\n%@\n%@\n%@\n%@\n%@", user0, user1, user2, user3, user4, user5, user6); // dismiss controller // update ui on other pages, and enable features } else { dispatch_async(dispatch_get_main_queue(), ^{ [self alertError]; }); } } else { dispatch_async(dispatch_get_main_queue(), ^{ [self alertError]; }); } } else { dispatch_async(dispatch_get_main_queue(), ^{ [self alertError]; }); } }]; } - (void)alertError { [[[UIAlertView alloc] initWithTitle:nil message:@"err, if it's not you it's me" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show]; } @end
Each service has a unique API and enpoints. There will be variations in the flow, and in the case of Social Framework and others there are abstracted convenience methods to reach the same goal. You can see, such as with the Google + API that utilizes OAuth2, that the process looks quite different from the previously demonstrated example. These abstractions that are available in the Frameworks will handle the presentation of the user login dialog, and the ability to make changes to the user's entry in the host User database:

- (void)goGoogle
{
    GPPSignIn *signIn = [GPPSignIn sharedInstance];
    signIn.delegate = self;
    signIn.shouldFetchGoogleUserEmail = YES;
    signIn.clientID = GOOGLE_CLIENTID;
    signIn.scopes = [NSArray arrayWithObjects:kGTLAuthScopePlusLogin,nil];
    signIn.actions = [NSArray arrayWithObjects:@"http://schemas.google.com/ListenActivity",nil];
    [signIn authenticate];
}
 
- (void)finishedWithAuth: (GTMOAuth2Authentication *)auth
                   error: (NSError *) error
{
    NSLog(@"Received error %@ and auth object %@",error, auth);
    NSString *urlStr = @"https://www.googleapis.com/oauth2/v1/userinfo?alt=json";
    NSURL *url = [NSURL URLWithString:urlStr];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [auth authorizeRequest:request
              completionHandler:^(NSError *error) {
                  NSString *output = nil;
                  if (error) {
                      output = [error description];
                  } else {
                      // Synchronous fetches like this are a really bad idea in Cocoa applications
                      //
                      // For a very easy async alternative, we could use GTMHTTPFetcher
                      NSURLResponse *response = nil;
                      NSData *data = [NSURLConnection sendSynchronousRequest:request
                                                           returningResponse:&response
                                                                       error:&error];
                      if (data) {
                          // API fetch succeeded
                          output = [[NSString alloc] initWithData:data
                                                          encoding:NSUTF8StringEncoding];
                        
                      }
                      
                      NSLog(@"output:%@",output);
                  }
                  
              }];
}

No comments:

Post a Comment