The Golden Spot

I hope this helps someone who is learning about Linux and web application programming with Python, Django, and Javascript.

Friday, November 25, 2011

Authentication redirects with iOS and Django

I am writing a client/server application. The client is an iPhone; client requests are handled by a Django application. Below, I will demonstrate how an iOS device can respond to redirects for login credentials from a Django service when a session has expired. I have not found much written about this subject so I am publishing what I have learned. This assumes that you know how to store and retrieve the username and password on an iOS device via Keychain Services, make HTTP requests from an iOS client, and create basic Django applications.

There are several questions that arise when acquiring a new session id with the client:
  1. How do we send username and password credentials to the Django service from the iOS client, such that we a) login to the server b) get forwarded to the original page we requested?
  2. What happens when the client requests a Django view with an @login_required decorator if the client's session id is expired?
  3. What happens to POST data in an original client request after the server sends login URL redirects while sending authentication challenges?
I hope the following post answers all of these. First, some definitions:
  • 'session id' - this refers to the hexadecimal string sent from the server as a cookie value, after the a user has signed-in/logged-in. (eg. "2b1189a188b44ad18c35e113ac6ceead"). More info on Django sessions
  • 'server' - in this case a Django app, hosted by Apache using mod_wsgi.
  • 'client' - in this case an iPhone (3GS), running the application and sending requests over a LAN. But theoretically, it could be any iOS networked device.
I will demonstrate with a URL that requires the user to be 'logged in' on the Django app. If the client does not have a valid session id, Django will respond by forwarding to a login view. In our case we will challenge the iOS client with a WWW-Authenticate; the client will respond to the challenge in a delegate method and be redirected to the original URL requested by the client. Inside a delegate method on the iOS client, I will show how to preserve the original request's HTTP method and (NSData *)HTTPBody .

Let's make this a simple HTTP GET request. In this case the requested URL will be:

http://10.0.0.2/user/home/


When the client sends the above request, the Django view will detect that the client is not logged in because either the session id was not sent with the GET request or the session id was expired. The server will respond with a status code of 302. In this case the server will respond with the redirect URL of the login page. Here is the @login_required decorator in the requested view:

Before we look at the Django sign in view, let's have a look at the NSURLConnection Delegate in the class that sends the request:

The delegate method is sending a new request containing the redirected URL if the server sends a redirect. And it is sending the original request if no redirect has been sent by the server. It is also sending a version of the original( very first ) request ( HTTPBody, HTTP method, and the latest header fields), only if the redirected URL is the same as the original request; This is how we keep any POST data in the HTTPBody when login redirects have succeeded. More on that below. Let's first take a look at the Django login view:


Notice how we send an authentication challenge from the view if no credentials have been sent by the client? Also, note that within httpd.conf, WSGIPassAuthorization must be set to 'on' for mod_wsgi to forward the credentials to the Django view.

Upon redirecting to the login URL, the client is presented with an authentication challenge. The client should respond appropriately within the NSURLConnection delegate method:
 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge


The client requests the redirected URL and sends the login credentials when receiving a WWW-Authentication challenge. Upon successful login, they are redirected to the original URL- which is stored by Django in the requests as 


request.GET['next'] = "/user/home/"


In the redirect for the original URL, after successful login, the Django view sends the cookie containing the session id. In the NSURLConnection delegate 


-(NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response;

all of the headers from the last request are copied to the newRequest; this includes the session id. If there was any POST data in the original request, it will also be copied to the newRequest, so that the original transaction data will be sent to the server after authenticating and acquiring a new session id.


I hope that this shed some light on how to keep your iOS app able to handle authentication redirects when the server session id expires. 


NOTE: I did not cover displaying a login view that allows the user to enter in username and password in the iOS client if the server login fails. Also, I did not show how to use the 

Labels: , , , , , ,