OAuth2, Django-RestFramework and iOS RestKit

Recently I implemented an OAuth2 authentication scheme on a REST API, I couldn’t find any nice examples of how to use this in iOS, so here’s what I did.

Django OAuth2 provider

To get Django RestFramework to play nicely, we need to configure a couple of things. First, install django-oauth2-provider

pip install django-oauth2-provider

And add it to your INSTALLED_APPS:

INSTALLED_APPS = (
    # ...
    'provider',
    'provider.oauth2',
)

Add the routes to your urls:

url(r'^oauth2/', include('provider.oauth2.urls', namespace = 'oauth2')),

And create the tables

$ python manage.py syncdb
$ python manage.py migrate

Now you can create a client, with a token and secret in the admin. You’ll need this info later for the authentication part.

Django RestFramework

As I wanted to still be able to use my Basic Authentication, I appended the OAuth class to the default authenticators. This is not necessary, but I wanted the change to be backwards compatible. Be aware that this might be a security issue (as you can work around the OAuth2 authentication), so be aware of that.

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.OAuth2Authentication',
    ),
}

And you’re all set. You should now be able to call the token endpoint with your credentials:

$ curl -X POST -d "client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=password&username=YOUR_USERNAME&password=YOUR_PASSWORD" http://localhost:8000/oauth2/access_token/

and get a token in return:

{"access_token": "<your-access-token>", "scope": "read", "expires_in": 86399, "refresh_token": "<your-refresh-token>"}

RestKit

Add AFOAuth2Client to your CocoaPods file:

pod 'AFOAuth2Client', '~> 0.1.2'

AFOAuth2Client is a simple layer on top of AFNetworking, so it’s quite easy to setup, once you know how.

In your App Delegate (or wherever you want to check the given credentials, say a LoginViewController), add this:

AFOAuth2Client *oauthClient = [AFOAuth2Client clientWithBaseURL:baseURL clientID:API_CLIENTID secret:API_SECRET];
[oauthClient authenticateUsingOAuthWithPath:@"/oauth2/token/"
    username:USERNAME
    password:PASSWORD
    scope:nil
    success:^(AFOAuthCredential *credential) {
        NSLog(@"I haz a token! %@", credential.accessToken);
        [AFOAuthCredential storeCredential:credential withIdentifier:oauthClient.serviceProviderIdentifier];
        [oauthClient setAuthorizationHeaderWithToken:credential.accessToken];
    }
    failure:^(NSError *error) {
        NSLog(@"Error: %@", error);
    }];

// assign the oauthclient to the default manager
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:oauthClient];
[oauthClient setDefaultHeader:@"Accept" value:RKMIMETypeJSON];

API_CLIENTID and API_SECRET are the credentials you created in the backend. The endpoint path should be good if you followed along.

Now, whenever you make a RestKit call, it will use the token you requested above. The only pitfall I noticed is that this call is asynchronous, so you need to check if there already is a token available, before calling any other request. I ended up pushing a message to my MasterView, which refreshed the tableview using the new token.