A JBoss Project
Red Hat

AeroGear iOS API Cookbook

The goal of this guide is to introduce you to the current available AeroGear iOS Core APIs.

A Pipe object represents a server connection. An object of this class is responsible to communicate with the server in order to perform read/write operations.

A Pipeline object represents a 'collection' of server connections (aka Pipes). It provides a standard way to communicate with the server no matter the data format or transport expected. It contains some simple management APIs to create and remove Pipe objects.

Creating a Pipeline and a Pipe object

To create a pipeline, you need to use the AGPipeline class. Below is an example:

// NSURL object:
NSURL* serverURL = [NSURL URLWithString:@"http://myserver.rhcloud.com/"];

// create the 'todo' pipeline, which points to the baseURL of the REST application
AGPipeline* todo = [AGPipeline pipelineWithBaseURL:serverURL];

// Add a REST pipe for the 'projects' endpoint
id<AGPipe> projects = [todo pipe:^(id<AGPipeConfig> config) {
    [config setName:@"projects"];
     // this is the default, can be emitted
    [config setType:@"REST"];
}];

In the example above the 'projects' pipe points to an RESTful endpoint (http://myserver.rhcloud.com/projects). However, technical details like RESTful APIs (e.g. HTTP PUT) are not exposed on the AGPipeline and AGPipe APIs. Below is shown how to get access to an actual pipe, from the AGPipeline object:

// get access to the 'projects' pipe
id<AGPipe> projects = [todo pipeWithName:@"projects"];

Read a specific object

The 'read:id' method allows you to fetch the object previously saved. The id of the object is used as the parameter.

// read an object with id of 1
[projects read:@"1" success:^(id responseObject) {
    // LOG the JSON response, returned from the server:
    NSLog(@"READ RESPONSE\n%@", [responseObject description]);
} failure:^(NSError *error) {
    // when an error occurs... at least log it to the console..
    NSLog(@"Read: An error occured! \n%@", error);
}];

In this case, where we have a RESTful pipe the API issues a HTTP GET request.

Read all objects from the server

The 'read' method allows to (currently) read all object from the server, of the underlying AGPipe:

[projects read:^(id responseObject) {
    // LOG the JSON response, returned from the server:
    NSLog(@"READ RESPONSE\n%@", [responseObject description]);
} failure:^(NSError *error) {
    // when an error occurs... at least log it to the console..
    NSLog(@"Read: An error occured! \n%@", error);
}];

Since we are pointing to a RESTful endpoint, the API issues a HTTP GET request.

Save object

The AGPipe offers an API to store objects on a remote server resource. Currently the objects are just simple map objects. In the future we are looking to support more advanced (complex) frameworks, like Core Data. The 'save' method is described below:

// create a dictionary and set some key/value data on it:
NSMutableDictionary* projectEntity = [@{@"title": @"Hello World"} mutableCopy];

// save the 'new' project:
[projects save:projectEntity success:^(id responseObject) {
    // LOG the JSON response, returned from the server:
    NSLog(@"CREATE RESPONSE\n%@", [responseObject description]);

    // get the id of the new project, from the JSON response...
    id resourceId = [responseObject valueForKey:@"id"];

    // and update the 'object', so that it knows its ID...
    [projectEntity setValue:[resourceId stringValue] forKey:@"id"];

} failure:^(NSError *error) {
    // when an error occurs... at least log it to the console..
    NSLog(@"SAVE: An error occured! \n%@", error);
}];

Above the save function stores the given NSDictionary on the server. In this case, where we have a RESTful pipe the API issues a HTTP POST request.

Upon success, we set the recordId property on the object from the server response. This is a required step cause this property is used by Pipe’s update and remove methods to perform operations on the object. By default, the Pipe is configured to use the name "id", but if your RESTful resource uses a different name, you can easily override it using the recordId configuration option when constructing the Pipe.

Update object

The 'save' method is also responsible for updating an 'object'. Before performing 'save', ensure that the object you are trying to save, has the recordId property set.

// change the title of the previous project 'object':
[projectEntity setValue:@"Hello Update World!" forKey:@"title"];

// and now update it on the server
[projects save:projectEntity success:^(id responseObject) {
    // LOG the JSON response, returned from the server:
    NSLog(@"UPDATE RESPONSE\n%@", [responseObject description]);
} failure:^(NSError *error) {
    // when an error occurs... at least log it to the console..
    NSLog(@"UPDATE: An error occured! \n%@", error);
}];

In this case, where we have a RESTful pipe the API issues a HTTP PUT request.

Remove object

The AGPipe also contains a 'remove' method to delete the object on the server. As in the case of 'update', ensure that the recordId property is set, so that it knows which resource to delete.

// Now, just remove the project:
[projects remove:projectEntity success:^(id responseObject) {
    // LOG the JSON response, returned from the server:
    NSLog(@"DELETE RESPONSE\n%@", [responseObject description]);
} failure:^(NSError *error) {
    // when an error occurs... at least log it to the console..
    NSLog(@"DELETE: An error occured! \n%@", error);
}];

In this case, where we have a RESTful pipe the API issues a HTTP DELETE request.

Multipart Upload

Support for multipart upload is also provided. The types of data that can be uploaded are local files, instances of NSData and NSInputStream. Let’s see how the upload mechanism works with an example:

// a multipart that contains a file
NSURL *file1 = <path to a local file>
AGFilePart *filePart = [[AGFilePart alloc]initWithFileURL:file1 name:@"myfile"];  // 1

// a multipart that contains an NSData object
NSData *data1 = [@"Lorem ipsum dolor sit amet.." dataUsingEncoding:NSUTF8StringEncoding];
AGFileDataPart *dataPart = [[AGFileDataPart alloc] initWithFileData:data1    // 2
                                                               name:@"data1"
                                                            fileName:@"data1.txt" mimeType:@"text/plain"];

// set up payload
NSDictionary *dict = @{@"somekey": @"somevalue",  // 3
                       @"another_key": @"some_other_key",
                       @"file1":filePart,
                       @"file2":dataPart};

// set an (optional) progress block  // 4
[[apiClient uploadPipe] setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
    NSLog(@"UPLOADPIPE Sent bytesWritten=%d totalBytesWritten=%qi of totalBytesExpectedToWrite=%qi bytes", bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}];

// upload data
[[apiClient uploadPipe] save:dict success:^(id responseObject) {
    NSLog(@"Successfully uploaded!");

} failure:^(NSError *error) {
    NSLog(@"An error has occured during upload! \n%@", error);
}];

An AGFilePart [1] and AGFileDataPart [2] objects are used to attach the data we want to upload. The former is initialized to point to a local file whereas the latter point to an NSData object respectively. Not shown in the example above, but an AGStreamPart can be also used to read the data from a NSInputStream directly.

For NSData we need to explicitly specify both the 'filename' and the 'MIME type', since they can not be automatically determined as with the case of a file.

After initialization of the objects, we simply attach them to the payload [3], setting an (optional) progress block [4] so we can get notified during the upload.

Prior to version 1.4 of the library, multipart upload was supported by the means of attaching a NSURL object directly on the payload. The method is still supported, but it is now deprecated and will be removed in the future versions of the library.

Timeout

Time out and Cancel pending operations

Timeout

During construction of the Pipe object, you can optionally specify a timeout interval (default is 60 secs) for an operation to complete. If the time interval is exceeded with no response from the server, then the failure callback is executed with an error code set to NSURLErrorTimedOut.

From the todo example above:

id<AGPipe> projects = [todo pipe:^(id<AGPipeConfig> config) {
    ...
    [config setTimeout:20];  // set the time interval to 20 secs
}];
If you are running on iOS versions < 6 and a timeout occurs on a pipe’s save operation, the error code is set to NSURLErrorCancelled.

Cancel

At any time after starting your operations, you can call 'cancel' on the Pipe object to cancel all running Pipe operations. Doing so will invoke the pipe’s 'failure' block with an error code set to 'NSURLErrorCancelled'. You can then check this code in order to perform your "cancellation" logic.

[projects read:^(id responseObject) {
    // LOG the JSON response, returned from the server:
    NSLog(@"READ RESPONSE\n%@", [responseObject description]);
} failure:^(NSError *error) {
    // when an error occurs... at least log it to the console..
    NSLog(@"Read: An error occured! \n%@", error);
}];

 // cancel the request
[projects cancel];

The library has built-in paging support, enabling the scrolling to either forward or backwards through a result set returned from the server. Paging metadata located in the server response (either in the headers via WebLinking RFC or custom headers, or in the body) are used to identify the next or the previous result set. For example, the Twitter search paging metadata is located in the body of the response signifying the next or previous result set. The location of this metadata as well as naming, is fully configurable during the creation of the pipe, thus enabling greater flexibility in supporting several different paging strategies.

Below is an example that goes against the AeroGear Controller Server.

NSURL* baseURL = [NSURL URLWithString:@"https://controller-aerogear.rhcloud.com/aerogear-controller-demo"];
AGPipeline* agPipeline = [AGPipeline pipelineWithBaseURL:baseURL];

id<AGPipe> cars = [agPipeline pipe:^(id<AGPipeConfig> config) {
    [config setName:@"cars-custom"];

    [config setPageConfig:^(id<AGPageConfig> pageConfig) {
        [pageConfig setNextIdentifier:@"AG-Links-Next"];
        [pageConfig setPreviousIdentifier:@"AG-Links-Previous"];
        [pageConfig setMetadataLocation:@"header"];
    }];
}];

Paging configuration parameters are encapsulated in the AGPageConfig object. Similar to the way we set Pipe configuration params by means of a block, paging configuration params are set using [AGPipeConfig setPageConfig:] method passing a block with the desired paging paramaters. Notice that in our example, we explicitely declare the name of the paging identifiers supported by the server, as well as the location of these identifiers in the response. If the metadataLocation is not specified, the library will assume the server is using Web Linking pagination strategy and will default to it.

For cases that a custom pagination strategy is followed in the server application , the library also allows the user to plug in a user-defined one, by the means of the [pageConfig setPageParameterExtractor] configuration setting. If set, the library will follow this strategy, overriding the build-in provided ones. In that case, the metadata location is not required and is ignored if set. The strategy should follow the protocol AGPageParameterExtractor, allowing the library to determine the 'next' and 'previous' parameters.

Start Paging

To kick-start pagination, you use the method readWithParams of the underlying AGPipe, passing your desired query parameters to the server. Upon successfully completion, the pagedResultSet (an enchached category of nsarray) will allow you to scroll through the result set.

__block NSMutableArray *pagedResultSet;

// fetch the first page
[cars readWithParams:@{@"color" : @"black", @"offset" : @"0", @"limit" : @1} success:^(id responseObject) {
    pagedResultSet = responseObject;

    // do something

} failure:^(NSError *error) {
    //handle error
}];

Move Forward in the result set

To move forward in the result set, you simple call next on the pagedResultSet :

// move to the next page
[pagedResultSet next:^(id responseObject) {
    // do something

} failure:^(NSError *error) {
    // handle error
}];

Move Backwards in the result set

To move backwards in the result set, you simple call previous on the pagedResultSet :

[pagedResultSet previous:^(id responseObject) {
    // do something

} failure:^(NSError *error) {
    // handle error
}];
The library uses the 'next' and 'previous' identifiers:
  • to extract the paging information params from the response and

  • filling those paging information params in the subsequent 'next' and 'previous' requests.

Failing to provide correct identifiers, means that no paging params would be appended in the request.

Without valid paging parameters the used service API may simply return the entire data. To ensure the results returned do 'logical' represent a 'next' and 'previous' page, the user must ensure correct identifiers are set in the Page configuration.

Further, moving beyond last or first page is left on the behaviour of the specific server implementation, that is the library will not treat it differently. Some servers can throw an error (like Twitter or AeroGear Controller does) by respondng with an http error response, or simply return an empty list. The user is responsible to cater for exception cases like this.

A Store represents an abstraction layer for a storage system. Currently implemented storage systems are an in-memory, property list and a SQLite storage (note that encrypted variants of these stores are also available, check the Cryptography support section below).

A DataManager manages different Store implementations. It is basically a factory that hides the concrete instantiations of a specific Store implementation. The class offers simple APIs to add, remove or get access to a 'data store'.

The code snippets below are part of aerogear-ios test suite. You can browse our BDD specifications for further samples.

Right now, there is NO automatic data sync. This is up to the user.

Create a datamanager with store object:

After receiving data from the server, your application may want to keep the data around. The AGDataManager API allows you to create AGStore instances. To create a datamanager, you need to use the AGDataManager class. Below is an example:

// create the datamanager
AGDataManager* dm = [AGDataManager manager];
// add a new (default) store object:
id<AGStore> myStore = [dm store:^(id<AGStoreConfig> config) {
    [config setName:@"tasks"];
}];

The AGDataManager class offers some simple 'management' APIs to work with containing AGStore objects. The API offers read and write functionality. The default implementation represents an "in-memory" store. If you want to create a AGPropertyListStorage or a AGSQLiteStorage, add a config type as shown below:

// create the datamanager
AGDataManager* dm = [AGDataManager manager];
// add a new (default) store object:
id<AGStore> store = [dm store:^(id<AGStoreConfig> config) {
   [config setName:@"tasks"];
   [config setType:@"SQLITE"];
}];

Storage can be of type: MEMORY, PLIST or SQLITE. Similar to the pipe API technical details of the underlying system are not exposed.

Save data to the Store

When using a pipe to read all entries of a endpoint, you can use the AGStore to save the received objects:

id<AGPipe> tasksPipe = [todo get:@"tasks"];

[tasksPipe read:^(id responseObject) {
    // the response object represents an NSArray,
    // containing multile 'Tasks' (as NSDictionary objects)

    // Save the response object to the store
    NSError *error;

    if (![myStore save:responseObject error:&error])
        NSLog(@"Save: An error occured during save! \n%@", error);

} failure:^(NSError *error) {
    // when an error occurs... at least log it to the console..
    NSLog(@"Read: An error occured! \n%@", error);
}];

When loading all tasks from the server, the AGStore object is used inside of the read block from the AGPipe object. The returned collection of tasks is stored inside our in-memory store, from where the data can be accessed.

Read an object from the Store

// read the task with the '0' ID:
id taskObject =  [myStore read:@"0"];

The read function accepts the recordID of the object you want to retrieve. If the object does not exist in the store, nil is returned.

If you want to read all the objects contained in the store, simply call the readAll function

// read all objects from the store
NSArray *objects = [myStore readAll];

Remove one object from the Store

The remove function allows you to delete a single entry in the collection, if present:

// remove the taskObject:
NSError *error;

if (![myStore remove:taskObject error:error])
    NSLog(@"Save: An error occured during remove! \n%@", error);

The remove method accepts a taskObject which is the object you want to remove. If the object does not exist in the store, FALSE is returned.

Filter the entire store

Filtering of the data available in the AGStore is also supported, by using the familiar NSPredicate class available in iOS. In the following example, after storing a pair of dictionaries representing user information details in the store (which can be easily come from a response from a server), we simple call the filter method to filter out the desired information:

 NSMutableDictionary *user1 = [@{@"id" : @"1",
                                @"name" : @"Robert",
                                @"city" : @"Boston",
                                @"department" : @{@"name" : @"Software", @"address" : @"Cornwell"},
                                @"experience" : @[@{@"language" : @"Java", @"level" : @"advanced"},
                                                  @{@"language" : @"C", @"level" : @"advanced"}]
                              } mutableCopy];

NSMutableDictionary *user2 = [@{@"id" : @"2",
                                @"name" : @"David",
                                @"city" : @"Boston",
                                @"department" : @{@"name" : @"Software", @"address" : @"Cornwell"},
                                @"experience" : @[@{@"language" : @"Java", @"level" : @"intermediate"},
                                                  @{@"language" : @"Python", @"level" : @"intermediate"}]
                              } mutableCopy];

NSMutableDictionary *user3 = [@{@"id" : @"3",
                                @"name" : @"Peter",
                                @"city" : @"Boston",
                                @"department" : @{@"name" : @"Software", @"address" : @"Branton"},
                                @"experience" : @[@{@"language" : @"Java", @"level" : @"advanced"},
                                                  @{@"language" : @"C", @"level" : @"intermediate"}]
                              } mutableCopy];

// save objects
BOOL success = [_memStore save:users error:nil];

if (success) { // if save succeeded, query the data
    NSPredicate *predicate = [NSPredicate
                              predicateWithFormat:@"city = 'Boston' AND department.name = 'Software' \
                              AND SUBQUERY(experience, $x, $x.language = 'Java' AND $x.level = 'advanced').@count > 0" ];

    NSArray *results = [_memStore filter:predicate];

    // The array now contains the dictionaries _user1_ and _user_3, since they both satisfy the query predicate.
    // do something with the 'results'
    // ...
}

Using NSPredicate to filter desired data, is a powerful mechanism offered in iOS and we strongly suggest to familiarize yourself with it, if not already. Take a look at Apple’s own documentation for more information.

Reset the entire store

The reset function allows you the erase all data available in the used AGStore object:

// clears the entire store
NSError *error;

if (![myStore reset:&error])
    NSLog(@"Reset: An error occured during reset! \n%@", error);

PropertyList Storage Specific

A simple Property list storage system is part of the library as well that uses the same 'AGStore' protocol for reading and writing. Depending on the type specified during store configuration, the system will use either NSPropertyListSerialization (type ''PLIST'') or NSJSONSerialization (type ''JSON'') when exporting data.

NOTE You must adhere to the rules governing the serialization of data types for each respective plist type.

The 'read', 'reset' or 'remove' API behave the same, as on the default ("in memory") store.

An Authenticator manages different authentication module implementations. It is basically a factory that hides the concrete instantiation of a specific Authentication Module implementation. The class offers simple APIs to add, remove, or get access to a 'authentication module'.

An AuthenticationModule represents an authentication module and provides the authentication and enrollment API. The default implementation uses REST as the auth transport. Similar to the Pipe, technical details of the underlying system are not exposed

Creating an authenticator with an authentication module

To create an authenticator, you need to use the AGAuthenticator class. Below is an example:

// create an authenticator object
AGAuthenticator* authenticator = [AGAuthenticator authenticator];

// add a new auth module and the required 'base url':
NSURL* baseURL = [NSURL URLWithString:@"https://myserver.rhcloud.com/"];
id<AGAuthenticationModule> myMod = [authenticator auth:^(id<AGAuthConfig> config) {
    [config setName:@"authMod"];
    [config setBaseURL:baseURL];
}];

The AGAuthenticator class offers some simple 'management' APIs to work with containing AGAuthenticationModule objects. The API provides an authentication and enrollment API. The default implementation uses REST as the auth transport. Similar to the pipe API technical details of the underlying system are not exposed.

Register a user

The enroll function of the 'AGAuthenticationModule' protocol is used to register new users with the backend:

// assemble the dictionary that has all the data for THIS particular user:

NSDictionary* userData = @{@"username": @"john",
						   @"password": @"123",
						   @"email", @"me@you.com",
						   @"betaAccountToken", @"21sda812sad24"};
// register a new user
[myMod enroll:userData success:^(id data) {
    // after a successful _registration_, we can work
    // with the returned data...
    NSLog(@"We got: %@", data);
} failure:^(NSError *error) {
    // when an error occurs... at least log it to the console..
    NSLog(@"SAVE: An error occured! \n%@", error);
}];

The enroll function submits a generic map object with contains all the information about the new user, that the server endpoint requires. The default (REST) auth module issues for the above a request against https://myserver.rhcloud.com/auth/enroll. Besides the NSDictionary the function accepts two simple blocks that are invoked on success or in case of a failure.

Login

Once you have a valid user you can use that information to issue a login against the server, to start accessing protected endpoints:

NSDictionary *credentials = @{@"username": @"john", @"password": @"123"};
// issuing a login
[myMod login:credentials success:^(id object) {
    // after a successful _login_, we can work
    // with the returned data...
} failure:^(NSError *error) {
    // when an error occurs... at least log it to the console..
    NSLog(@"SAVE: An error occured! \n%@", error);
}];

The default (REST) auth module issues for the above a request against https://myserver.rhcloud.com/auth/login. Besides the username and the password (stored in a NSDictionary), the function accepts two simple blocks that are invoked on success or in case of a failure.

Pass the auth module to a pipe

After running a successful login, you can start using the AGAuthenticationModule object on a AGPipe object to access protected endpoints:

id<AGPipe> tasks = [pipeline pipe:^(id<AGPipeConfig> config) {
    [config setName:@"tasks"];
    [config setBaseURL:serverURL];
    [config setAuthModule:myMod];
}];

[tasks read:^(id responseObject) {
    // LOG the JSON response, returned from the server:
    NSLog(@"READ RESPONSE\n%@", [responseObject description]);
} failure:^(NSError *error) {
    // when an error occurs... at least log it to the console..
    NSLog(@"Read: An error occured! \n%@", error);
}];

When creating a pipe you need to use the authModule argument in order to pass in an AGAuthenticationModule object.

Logout

The logout from the server can be archived by using the logout function:

// logout:
[myMod logout:^{
    // after a successful _logout_, when can notify the application
} failure:^(NSError *error) {
    // when an error occurs... at least log it to the console..
    NSLog(@"SAVE: An error occured! \n%@", error);
}];

The default (REST) auth module issues for the above a request against https://myserver.rhcloud.com/auth/logout. The function accepts two simple blocks that are invoked on success or in case of a failure.

NSURLCredential support.

As of version 1.1.0 of the library, you can pass directly on the Pipe configuration an NSURLCredential object. Doing that will give you instant support for the built-in authentication protocols already supported in iOS (such as Basic/Digest). When a request comes in where an authentication challenge is required, the library will pass to the server those credentials.

To pass the NSURLCredential object, use the setCredential configuration method during construction of the Pipe:

id<AGPipe> tasks = [pipeline pipe:^(id<AGPipeConfig> config) {
    [config setName:@"tasks"];
    [config setCredential:[NSURLCredential
                credentialWithUser:@"username" password:@"passwd" persistence:NSURLCredentialPersistenceNone];
}];
Care should be taken when specifying the persistence type param when constructing the NSURLCredential object. Specifying type other than NSURLCredentialPersistenceNone, will have the effect of the credentials to be preserved across session and application restarts. In that case, the developer is responsible to clear the cache. See NSURLCredentialStorage class reference for more information.

Time out and Cancel pending operations

As with the case of Pipe, configured timeout interval (in the config object) and cancel operation in AGAuthenticationModule is supported too.

An Authorizer manages different authorization module implementations. It is basically a factory that hides the concrete instantiation of a specific AGAuthorizer Module implementation. The class offers simple APIs to add, remove, or get access to a 'authorization module'.

The AuthorizationModule current implementation is based on OAuth2 protocol and uses REST as the authz transport. Similar to the Pipe, technical details of the underlying system are not exposed.

Creating an Authorizer

To create an Authorizer, you need to use the AGAuthorizer class. Below is an example:

// create an authenticator object
AGAuthorizer* authorizer = [AGAuthorizer authorizer];

_restAuthzModule = [authorizer authz:^(id<AGAuthzConfig> config) {
    config.name = @"restAuthMod";
    config.baseURL = [[NSURL alloc] initWithString:@"https://accounts.google.com"];
    config.authzEndpoint = @"/o/oauth2/auth";
    config.accessTokenEndpoint = @"/o/oauth2/token";
    config.revokeTokenEndpoint = @"/o/oauth2/revoke";
    config.clientId = @"XXX";
    config.redirectURL = @"YYY:/oauth2Callback";
    config.scopes = @[@"https://www.googleapis.com/auth/drive"];
}];

Creating an Account Manager

When authorizing an OAuth2 application, you often want the user to grant access for your app only once in the app lifetime. This is the whole purpose of AccountManager — you can create multiple accounts and store them in an AGStore. It’s up to you to choose you store. By default all the sensitive data is stored in the Keychain if the iPhone passcode is set.

Alternatively, you can use an encrypted storage for storing authorization tokens, but be aware that the user’s password must be provided.

Here is how you do it:

    // set up crypto params configuration object
    AGPassphraseCryptoConfig *config = [[AGPassphraseCryptoConfig alloc] init];
    [config setSalt:[self salt]];
    [config setPassphrase:passphrase];

    // initialize the encryption service passing the config
    id<AGEncryptionService> encService = [[AGKeyManager manager] keyService:config];        // [1]

    // access Store Manager
    AGDataManager *manager = [AGDataManager manager];

    // create store
    id<AGStore> store = [manager store:^(id<AGStoreConfig> config) {                        // [2]
        [config setName:@"OAuthStorage"];
        // can also be set to "ENCRYPTED_SQLITE" for the encrypted sqlite variant
        [config setType:@"ENCRYPTED_PLIST"];
        [config setEncryptionService:encService];
    }];

    // initialize account manager with encrypted store backend
    _acctManager = [AGAccountManager manager:store];                                        // [3]

    // set up facebook authz modules
    id<AGAuthzModule> facebookAuthzModule = [_acctManager authz:^(id<AGAuthzConfig> config) { // [4]
        config.name = @"restAuthMod";
        config.baseURL = [[NSURL alloc] init];
        ...
    }];

In [1] you create an encryption service to be used with the encrypted database.

In [2] you create an encrypted storage passing the encryption service.

Last [3] create an AccountManager by injecting the created database. You can then create an authz module using the same code as seen with Authorizer above.

Pass AGAuthzModule to a pipe

Authorization module seamlessly integrates with Pipe, all you need to do is [5] inject your AuthzModule into the Pipe.

// set up our facebook pipeline and pipes
AGPipeline *_fbPipeline = [AGPipeline pipelineWithBaseURL:[NSURL URLWithString:@"https://graph.facebook.com/me/"]];

[_fbPipeline pipe:^(id<AGPipeConfig> config) {
    [config setName:@"facebookUploadPipe"];
    [config setEndpoint:@"photos"];
    [config setAuthzModule:facebookAuthzModule];                    // [5]
}];

Grant access is implicit by default

You may ask when does Authz module request for authorization? Basically each time you do a CRUD operation on Pipe, tokens are checked. If there are no tokens available you will be prompted. If the tokens have expired, a refresh token action is performed under the hood. Once you’ve done all the configuration, you can use the Pipe as usual.

Grant access explicitly

However if you want to trigger the grant pop-up, the explicit call to requestAccessSuccess:failure is required, as shown below:

[_restAuthzModule requestAccessSuccess:^(id object) {
    // Do some work here
} failure:^(NSError *error) {
    // Deal with failure
}];

Revoke access

You may want to revoke access tokens for you app by calling revokeAccessSuccess:failure as shown below:

[_restAuthzModule revokeAccessSuccess:^(id object) {
    // Do some work here
} failure:^(NSError *error) {
    // Deal with failure
}]

Since 1.3.0 release of the library, we’ve built on the security foundation provided by aerogear-crypto-ios to offer encrypted variants of our existing in-memory, plist and sqlite data stores. If you have been using the existing data stores to persist your data, you will be happy to know that you can easily switch to an encrypted variant by simple changing the type and some small (really!) amount of code to provide the necessary crypto params. Let’s start with a small primer to symmetric encryption, which is primary cryptography mechanism used to encrypt the data stores.

Symmetric encryption API

Symmetric encryption (also called private key encryption) is when the same key is used to encrypt and decrypt data. It is best defined with the following concepts:

  • Encryption key is a block of bytes of a specific length. Key can be derived from password using for example, PBKDF2 algorithm. Must be kept secret.

  • IV (Initialization Vector) is a random value that is used to encrypt data. Encryption algorithms usually work on fixed-size blocks, IV defines the first encrypted block.

You can derive the encryption key using a password or passphrase and salt with AGPBKDF2.

  • Password is easy to remember and usually defined by user and must be kept secret. A password is not a key.

  • Salt is a random value that is used together with a password to derive an encryption key. A salt value does not need to be kept secret.

AeroGear-Crypto symmetric encryption is powered by NaCl. Using Curve 25519 algorithms, it provides you an easy way to encrypt your sensitive data. No need to be a security expert to use the library. See by yourself.

First of all, to encrypt your data you need an encryption key. Your key can be derived from a PIN, password or passphrase using AGPBKDF2.

    NSData *salt = [AGRandomGenerator randomBytes];
    NSData *encryptionKey = [pbkdf2 deriveKey:@"password4me" salt:salt];

For random generation of key, salt or IV, use AGRandomGenerator. By default, AGRandomGenerator generates 16 bytes key, but you can also specify the length if you wish.

Once you’ve got your encryption key, use AGSecretBox to do the actual encryption. Its interface is simple:

@interface AGSecretBox : NSObject
- (id)initWithKey:(NSData*)key;
- (NSData*)encrypt:(NSData*)data IV:(NSData*)IV;
- (NSData*)decrypt:(NSData*)data IV:(NSData*)IV;
@end

With AGSecretBox, you can encrypt/decrypt data using your encryption key and a randomly generated IV (Initialization Vector) as shown below:

    NSString* stringToEncrypt = @"I want to keep it secret";
    // encode string into data
    NSData* dataToEncrypt = [stringToEncrypt dataUsingEncoding:NSUTF8StringEncoding];
    NSData* IV = [AGRandomGenerator randomBytes:16];

    // init secretbox with a key
    AGSecretBox* secretBox = [[AGSecretBox alloc] initWithKey:encryptionKey];

    // encrypt
    NSData* encryptedData = [secretBox encrypt:dataToEncrypt IV:IV];

    // decrypt
    NSData* decryptedData = [secretBox decrypt:encryptedData IV:IV];
To be able to decrypt, you need the randomly generated IV and you can regenerate the key with the salt and the password (prompted on the fly). It is not recommended to store either password or derived key. Salt and IV are not security sensitive in the sense that they can be stored.

Encrypted storage

If your storage information need to be encrypted, AeroGear provides convenient and transparent encrypted storage support. You can use encrypted stores with the same API as the plain ones.

Current available implementations are:

  1. Encrypted Memory storage which allows you to work in-memory with sensitive data.

  2. Encrypted Property List storage implementation.

  3. Encrypted SQLite storage implementation.

Prior to using an encrypted data store variant, we need to obtain an instance of an EncryptionService which we will set it as a configuration parameter when we first initialise the store. The store will then use that service to perform encryption and decryption of data. Let’s see how this works:

    // randomly generate salt
    NSData *salt = [AGRandomGenerator randomBytes];  // [1]

    // set up crypto params configuration object
    AGPassphraseCryptoConfig *config = [[AGPassphraseCryptoConfig alloc] init];  // [2]
    [config setSalt:salt];  // 3
    [config setPassphrase:self.password.text];   // 4

    // initialize the encryption service passing the config
    id<AGEncryptionService> encService = [[AGKeyManager manager] keyService:config];  // [5]

    // access Store Manager
    AGDataManager *manager = [AGDataManager manager];  // 6

    // create store
    store = [manager store:^(id<AGStoreConfig> config) {
        [config setName:@"CredentialsStorage"];
        [config setType:@"ENCRYPTED_PLIST"];  // 7
        [config setEncryptionService:encService];  // 8
    }];

    // ok time to attempt reading..
    NSArray *data = [store readAll]) { // 9

    if (data)
        // decryption succeeded!

In [1] we initialize a random salt that will be used in the encryption. In [2] we initialize an instance of a CryptoConfig configuration object to set our crypto params. Here we use an PassphraseCryptoConfig object, that sets the necessary crypto params for the PBKDF2 Encryption Service, mainly the salt [3] and the passphrase [4].

Now that we have setup the configuration, it’s time to obtain an instance of an EncryptionService and that’s exactly what we do in [5]. KeyManager parses the configuration and returns an instance of it. Because we passed an PassphraseCryptoConfig object, a PBKDF2 encryption service would be returned.

In [6] we initialize our data store (an encrypted plist [7]), setting the encryption service we obtained earlier [8]. Reading and saving operations are done like all the other stores, but this time the data are transparently encrypted/decrypted.

In [9] we attempt to read data from the store. If that fails, then user supplied wrong crypto parameters (either passphrase or salt).

AeroGear Crypto Password

If you want to see it all together, give a try to AeroGear Crypto Password app. This application shows you how using encrypted storage you can store in a central point all your passwords.

redhatlogo-wite