All posts by Paul

oAuth2 with iOS (Part 3)

Within this post we continue working on the iOS app. For previous posts in this series, see here :).
Within this third part we will start with integrating oAuth2 into the app.

Like before, all code can be found in GIT. The code like at the end of the project can be found here.

To implement oAuth2 into our app, we are going to use a library from Toto.  So lets add this as submodule into our git repo:

MacBook-Air-van-Paul:Symfony iOS Oauth Demo paulsohier$ git submodule add https://github.com/nxtbgthng/OAuth2Client.git
Cloning into 'Symfony iOS Oauth Demo/OAuth2Client'...
remote: Reusing existing pack: 1858, done.
remote: Counting objects: 31, done.
remote: Compressing objects: 100% (25/25), done.
remote: Total 1889 (delta 16), reused 10 (delta 6)
Receiving objects: 100% (1889/1889), 463.92 KiB | 341.00 KiB/s, done.
Resolving deltas: 100% (1193/1193), done.
Checking connectivity... done.

Now we can just drag the OAuth2Client.xcodeproj into your Xcode, and it is included in the project.
Toto included some instructions in his README to build to library (We did already the first two item!):

  • Place the OAuth2Client folder within your source root
  • Drag the OAuth2Client.xcodeproj into your project
  • Under your build target, select the Build Phases tab.
    • Under Target Dependencies add OAuth2Client
    • Under Link Binary With Libraries, add libOAuth2Client.a
  • Under Build Settings,
    • Add $(SRCROOT)/path/to/OAuth2Client Header Search Paths, set as recursive
    • Add -ObjC to Other Linker Flags
  • #import "NXOAuth2.h"
  • add the Security.framework as a build dependency

Now, lets try to use the library. In AppDelegate.m we add the following method: (Yes, the clientID and secret are still wrong).

+ (void)initialize;
{
    [[NXOAuth2AccountStore sharedStore] setClientID:@"xXxXxXxXxXxX"
                                             secret:@"xXxXxXxXxXxX"
                                   authorizationURL:[NSURL URLWithString:@"http://ip-6.nl/app_dev.php/oauth/v2/auth"]
                                           tokenURL:[NSURL URLWithString:@"http://ip-6.nl/app_dev.php/oauth/v2/token"]
                                        redirectURL:[NSURL URLWithString:@"https://...your redirect URL..."]
                                     forAccountType:@"oauthDemoService"];
    NSLog(@"Init done.");
}

When you try to run this code, everything should work fine, and your app should start. You should not see anything thats changed however.

So, lets create a new client. We did this before in the second part of the android app.

ipv6:/var/ip6/SymfonyOauthDemo# php app/console oauth-example:oauth-server:client:create --redirect-uri="http://ios.local" --grant-type="authorization_code" --grant-type="password" --grant-type="refresh_token" --grant-type="token" --grant-type="client_credentials"
Added a new client with public id 5_3mtmm27tp90kosg0wgcwg4ocwg08g4g4o8coocs48g8scco4ws, secret 294jb6g99bk04oko4sss0wckockoog80o84w48ksww4cckkggs

So, now we have a client ID, lets try it out. This is very simple, by calling requestAccessToAccountWithType. Lets do this in viewDidAppear in our main view controller:

- (void)viewDidAppear:(BOOL)animated
{
    [[NXOAuth2AccountStore sharedStore] requestAccessToAccountWithType:@"oauthDemoService"];
}

Now, lets see if it works. Once we open the app, it will nicely redirects us to a browser window with our symfony2 site. If you login and click allow, it will redirect you to http://ios.local. But hey, thats not what we want, is it? We want it to redirect back to your app. While in our android app we had the view directly in the app, with iOS we switch apps. This means we need to have something to switch back to our app. Luckily this is very easy with iOS. The only thing to do is register our own URL scheme. This is done in info.plist.

  • On the top of info.plist, click the add sign. Select here that you want to define a URL types. A new list is added.
  • You see item0, expand this item.
  • This is a dictionary with item, first we need to set the URL Identifier. Set this to something unique for your app. I did choose me.sohier.siod
  • Now click on the + sign to add a new item. Choose as key for URL schemes.
  • In item 0, we assign our scheme. In my case I choose for siod.

plist.info
If you have done everything right, it should  look like on the image left.
Now, we have a different redirect URL. Due to this, we need to create a new client with the changed URL:

ipv6:/var/ip6/SymfonyOauthDemo# php app/console oauth-example:oauth-server:client:create --redirect-uri="siod://ios.local" --grant-type="authorization_code" --grant-type="password" --grant-type="refresh_token" --grant-type="token" --grant-type="client_credentials"
Added a new client with public id 6_5xqk9omvc7wg48gs8wc88wck0k8ggs44gso0404w4gcsc0088, secret 2dw92x1w4u4g8g0o8wkkokook0co84g40kcg84g4wc0ok0okw8

Notice here the URL. After that, we updated our configuration in the delegate:

+ (void)initialize;
{
    [[NXOAuth2AccountStore sharedStore] setClientID:@"6_5xqk9omvc7wg48gs8wc88wck0k8ggs44gso0404w4gcsc0088"
                                             secret:@"2dw92x1w4u4g8g0o8wkkokook0co84g40kcg84g4wc0ok0okw8"
                                   authorizationURL:[NSURL URLWithString:@"http://ip-6.nl/app_dev.php/oauth/v2/auth"]
                                           tokenURL:[NSURL URLWithString:@"http://ip-6.nl/app_dev.php/oauth/v2/token"]
                                        redirectURL:[NSURL URLWithString:@"siod://ios.local"]
                                     forAccountType:@"oauthDemoService"];
    NSLog(@"Init done.");
}

So, lets see if everything works now. When starting the app again, we are redirecting to safari. After logging in and clicking allow we are send back to the app. So it looks like it works. But do we have our user details now? We didn’t add any code for it yet, so I guess not, hmm?

When the library received a client detail back, it sends a notification, in both a error case as a success case. So lets listen to these notifications in our view controller:

    [[NSNotificationCenter defaultCenter] addObserverForName:NXOAuth2AccountStoreAccountsDidChangeNotification
                                                      object:[NXOAuth2AccountStore sharedStore]
                                                       queue:nil
                                                  usingBlock:^(NSNotification *aNotification){
                                                      // Update your UI
                                                      
                                                      NSLog(@"Received OK.");
                                                  }];
    
    [[NSNotificationCenter defaultCenter] addObserverForName:NXOAuth2AccountStoreDidFailToRequestAccessNotification
                                                      object:[NXOAuth2AccountStore sharedStore]
                                                       queue:nil
                                                  usingBlock:^(NSNotification *aNotification){
                                                      NSError *error = [aNotification.userInfo objectForKey:NXOAuth2AccountStoreErrorKey];
                                                      // Do something with the error
                                                      NSLog(@"Received ERR.");
                                                  }];

We will, for now, just do a NSLog, while testing. So, now we followed all instructions from Toto. So my assumption is that we should see a nice NSLog item. Badly enough, while testing, I didn’t get a log item at all. After some more reading, and finding a few bug reports, you needed to do another step. It is, if you read well, also noted within the instructions:

If you are using an external browser, your application needs to handle the URL you have registered as an redirect URL for the account type. The service will redirect to that URL after the authentication process.

What this means is that we need to add another method to our AppDelegate:

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url;
{
    NSLog(@"Handling");
    
    BOOL handled = [[NXOAuth2AccountStore sharedStore] handleRedirectURL:url];
    
    if (!handled) {
        NSLog(@"The URL (%@) could not be handled. Maybe you want to do something with it.", url);
    }
    else
    {
        NSLog(@"Handled");
    }
    
    return handled;
}

This will handle the case when the app is opened by a URL. So lets give it another test. Now, after we are logged in, we should see several log items, with the last one being “Received OK.”. With my test, this was indeed the case:

2014-06-22 13:44:47.528 Symfony iOS Oauth Demo[665:346183] Handling
2014-06-22 13:44:47.539 Symfony iOS Oauth Demo[665:346183] Handled
2014-06-22 13:44:47.682 Symfony iOS Oauth Demo[665:346292] -[NXOAuth2PostBodyStream open] Stream has been reopened after close
2014-06-22 13:44:47.873 Symfony iOS Oauth Demo[665:346183] Received OK.

So, it looks like everything works. Now lets modify our code a bit. If we call multiple times the requestAccessToAccountWithType method, we will end up with several requests. Thats not something we want. We want to make sure we only have one, and don’t call it when we already have a account.

Lets add a second property to the interface:

@property (nonatomic, strong) NXOAuth2Account *account;

Now, lets modify our viewDidAppear:

- (void)viewDidAppear:(BOOL)animated
{
    for (NXOAuth2Account *acc in [[NXOAuth2AccountStore sharedStore] accountsWithAccountType:@"oauthDemoService"]) {
        self.account = acc;
        NSLog(@"Found a account");
        //[[NXOAuth2AccountStore sharedStore] removeAccount:acc];
        break;
    };

    if (!self.account)
    {
        [[NXOAuth2AccountStore sharedStore] requestAccessToAccountWithType:@"oauthDemoService"];
        
        [[NSNotificationCenter defaultCenter] addObserverForName:NXOAuth2AccountStoreAccountsDidChangeNotification
                                                          object:[NXOAuth2AccountStore sharedStore]
                                                           queue:nil
                                                      usingBlock:^(NSNotification *aNotification){
                                                          NSLog(@"Received OK.");
                                                          
                                                          id acc = [aNotification.userInfo objectForKey:@"NXOAuth2AccountStoreNewAccountUserInfoKey"];
                                                          if ([acc isKindOfClass:[NXOAuth2Account class]])
                                                          {
                                                              self.account = (NXOAuth2Account *) acc;
                                                              NSLog(@"Name: %@", self.account.accountType);
                                                          }
                                                          else
                                                          {
                                                              NSLog(@"Weird item");
                                                          }
                                                      }];
        
        [[NSNotificationCenter defaultCenter] addObserverForName:NXOAuth2AccountStoreDidFailToRequestAccessNotification
                                                          object:[NXOAuth2AccountStore sharedStore]
                                                           queue:nil
                                                      usingBlock:^(NSNotification *aNotification){
                                                          NSError *error = [aNotification.userInfo objectForKey:NXOAuth2AccountStoreErrorKey];
                                                          // Do something with the error
                                                          NSLog(@"Received ERR.");
                                                      }];
    }
}

So, when we find a account for our type, we assign it to our new property. If there is no account yet, we start with asking for one, and then assign it.

Before, our full UI was available while there was no account. So, when there is no account, we want to disable the add button. We also don’t want to initialise the fetch request yet.

First of, we need to modify the storyboard to disable to Add button by default. This can be simply be done by setting enabled to false. After that we need a outlet to the button, so we can enable it later on. This can be done by ctrl dragging into the code.

Now, if we start the app, you will notice the button is greyed out. So lets fix that. We need to modify both the setter for the managedContext, as for the account.
So, lets replace the setter for the managedObjectContext with this new code:

- (void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
    
    _managedObjectContext = managedObjectContext;
    
    [self updateUI];
}
- (void)setAccount:(NXOAuth2Account *)account
{
    _account = account;
    
    [self updateUI];
}

- (void)updateUI
{
    if (self.managedObjectContext && self.account)
    {
        self.debug = YES;
        
        
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Demo"];
        request.predicate = nil;
        request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"title"
                                                                  ascending:YES
                                                                   selector:@selector(localizedStandardCompare:)]];
        
        self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                            managedObjectContext:self.managedObjectContext
                                                                              sectionNameKeyPath:nil
                                                                                       cacheName:nil];
        
        self.addButton.enabled = YES;
    }
}

As you can see, I moved the initialisation of the fetchResultsController to the new method updateUI. This runs only when both the context as the account is available. It also enabled the add button.

Lastly, for this part of the series, we are going to update the interface to the view controller for the create method. We want the account here as well, so lets add it to there interface in CreateDemoViewController.h:

@property (nonatomic, strong) NXOAuth2Account *account;

And assign it in prepareForSegue:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"create-new-demo"])
    {
        CreateDemoViewController *vw = (CreateDemoViewController *) segue.destinationViewController;
        vw.managedObjectContext = self.managedObjectContext;
        vw.account = self.account;
    }
}

Now the app has fully support for oAuth2, the user account is saved locally, and we can continue with actually getting data from the server. But, thats for the next part in this series.

oAuth2 with iOS (part 2)

Within this post we continue working on the iOS app. For previous posts in this series, see here :).
Apple announced the second BETA for iOS8 last tuesday, and due to that, the devices I use for this example are upgraded to this second BETA.

Like before, all code can be found in GIT. The code like at the end of the project can be found here.

So, what are we going to do in this second part? First, we are going to create some basic functionality in the app to add a new demo. Second, we are going to write some tests, including some performance tests, to make sure everything works, even after we are modifying it to work with our network code (Which will be later in a next part). Creating tests for apps (And other code as well of course) is always a great idea. With the next xCode feature apple introduces in iOS8/xCode6 you can do lots of great stuff. I am going to use some of these features to do testing of this demo app.
In a other post, which is not within this series, but still interesting, I will explain how to use xCode server for continues integration with iOS8 and github, because that is (badly enough) not as straight forward as it can be.

Creating a new item in a core data model isn’t very difficult. Because we overwrite the Demo class anytime we generate it, that is not a good location to add it, isn’t it? well, it actually is, but we are adding a category for that demo class, and use that instead.
So lets create a new Cacao class, and name it Demo+Create. Last time, we had a problem creating a new category. This issue doesn’t seem to be fixed yet in the second BETA, so we need to do it by hand. Continue reading

oAuth2 with iOS (part 1)

So, now we are finished with the Android app, we start with creating the iOS app. For previous posts in this series, see here :).
Currently, Apple announced iOS 8 with their new swift language and a new xCode on WWDC a few days ago. While I am not going to use Swift for this tutorial, I will use Xcode 6 and iOS 8. I haven’t tried any of this code on previous iOS/xcode versions, however I guess it should not be a issue.

Lets start with the basics. We will use the same approach as with the Android app. We have a local Database (In this case we use core data, more on that later on), a simple table view, and a few buttons to add or delete data.
To implement the Core Data stuff, I use some code from tutorials created by Paul Hegarty. I won’t be going very deep into the Core Data related stuff, it is explained very well by Paul.

All code and examples I provide here are tested/shown on a iPad mini retina or a iPhone 5S. Both have iOS 8 Beta 1.

Like with he Android app, all code can be found in GIT. The code like at the end of the project can be found here.

Continue reading