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.