Question

I am using the Facebook iOS SDK to POST the Facebook Access Token to my Django server URI. The corresponding views.py function is shown below and I get a 200 Response code when I do the POST from iOS. However, I have a second @login_required decorated URI that I call from the iOS Device immediately afterword which considers me not logged in and redirects me to my main page. What am I doing wrong? How do I 'stay' logged in after my successful POST from iOS?

# For POSTing the facebook token
from django.views.decorators.csrf import csrf_exempt
from allauth.socialaccount import providers
from allauth.socialaccount.models import SocialLogin, SocialToken, SocialApp
from allauth.socialaccount.providers.facebook.views import fb_complete_login
from allauth.socialaccount.helpers import complete_social_login

# Log in from Facebook
@csrf_exempt
def mobile_facebook_login(request):
    response = HttpResponse() ## Create an HTTP Response Object
    if request.method == "POST": # The method better be a POST
        access_token = request.POST.get('access_token') # Get token   
        try:
            app = SocialApp.objects.get(provider="facebook")
            token = SocialToken(app=app, token=access_token)

            # Check token against facebook                  
            login = fb_complete_login(request, app, token)
            login.token = token
            login.state = SocialLogin.state_from_request(request)

            # Add or update the user into users table
            ret = complete_social_login(request, login)

            # If we get here we've succeeded
            response['Auth-Response'] = 'success'
            response.status_code = 200 # Set status  
            return response   
        except Exception,e:
            # If we get here we've failed
            response['Auth-Response'] = 'failure: %s'%(e)
            response.status_code = 401 # Set status
            return response
    else:
        # If we get here we've failed
        response['Auth-Response'] = 'failure'
        response.status_code = 401 # Set status
        return response

======= UPDATE ==========

Ok, thanks for the comments. So I am now POSTing the facebook email address as well and getting the user and logging them in manually. However, subsequent requests STILL are not authenticated. So the @login_required decorator still fails.. Any other ideas?

# Log in from Facebook
@csrf_exempt
def mobile_facebook_login(request):
    response = HttpResponse() ## Create an HTTP Response Object
    if request.method == "POST": # The method better be a POST
        access_token = request.POST.get('access_token') # Get token
        email = request.POST.get('email') # Get email
        try:
            app = SocialApp.objects.get(provider="facebook")
            token = SocialToken(app=app, token=access_token)

            # Check token against facebook                  
            login = fb_complete_login(request, app, token)
            login.token = token
            login.state = SocialLogin.state_from_request(request)

            # Add or update the user into users table
            ret = complete_social_login(request, login)

            # Try to get username from email
            try:
                        user = User.objects.get(email=email) # Get User
                # Login the user from Django's perspective
                user.backend = 'django.contrib.auth.backends.ModelBackend'
                auth_login(request,user)
                except User.DoesNotExist:
                        # If we get here we've failed
                response['Auth-Response'] = 'failure: %s'%(e)
                response.status_code = 401 # Set status
                return response

            # If we get here we've succeeded
            response['Auth-Response'] = 'success'
            response.status_code = 200 # Set status  
            return response   
        except Exception,e:
            # If we get here we've failed
            response['Auth-Response'] = 'failure: %s'%(e)
            response.status_code = 401 # Set status
            return response
    else:
        # If we get here we've failed
        response['Auth-Response'] = 'failure'
        response.status_code = 401 # Set status
        return response

==== Another Update ==========

Based on the 2nd answer in this post: django authentication without a password

I created a custom login backend that does not require a password. The 3rd answer in that post discusses how doing this:

user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)

Doesn't store the login validation in the session. So I tried using a custom backend.

Here is my modified code:

# Log in from Facebook
@csrf_exempt
def mobile_facebook_login(request):
    response = HttpResponse() ## Create an HTTP Response Object
    if request.method == "POST": # The method better be a POST
        access_token = request.POST.get('access_token') # Get token
        email = request.POST.get('email') # Get email
        try:
            app = SocialApp.objects.get(provider="facebook")
            token = SocialToken(app=app, token=access_token)

            # Check token against facebook                  
            login = fb_complete_login(request, app, token)
            login.token = token
            login.state = SocialLogin.state_from_request(request)

            # Add or update the user into users table
            ret = complete_social_login(request, login)

            # Try to get username from email
            try:
                        user = User.objects.get(email=email) # Get User
                # Login the user from Django's perspective
                user.backend = 'django_tours.auth_backend.PasswordlessAuthBackend'
                user = authenticate(email=user.email)
                auth_login(request,user)
                #request.session.cycle_key()
                    except User.DoesNotExist:
                        # If we get here we've failed
                response['Auth-Response'] = 'failure: %s'%(e)
                response.status_code = 401 # Set status
                return response

            # If we get here we've succeeded
            response['Auth-Response'] = 'success'
            response['User-Is-Authenticated'] = '%s'%(request.user.is_authenticated())
            response.status_code = 200 # Set status  
            return response   
        except Exception,e:
            # If we get here we've failed
            response['Auth-Response'] = 'failure: %s'%(e)
            response.status_code = 401 # Set status
            return response
    else:
        # If we get here we've failed
        response['Auth-Response'] = 'failure'
        response.status_code = 401 # Set status
        return response

Using hurl.it I get this HTTP 200 response, but am still NOT considered logged in from the iPhone:

Auth-Response: success
Content-Encoding: gzip
Content-Length: 20
Content-Type: text/html; charset=utf-8
Date: Thu, 08 May 2014 00:22:47 GMT
Server: Apache/2.2.22 (Ubuntu)
Set-Cookie: csrftoken=UuJDP6OB3YCSDtXLEa10MgJ70tDtIfZX; expires=Thu, 07-May-2015 00:22:48 GMT; Max-Age=31449600; Path=/, sessionid=kdr061v1pcsbqtvgsn3pyyqj9237z6k8; expires=Thu, 22-May-2014 00:22:48 GMT; httponly; Max-Age=1209600; Path=/, messages="4f919699a4730a3df220a0eb3799ed59d2756825$[[\"__json_message\"\0540\05425\054\"Successfully signed in as philbot.\"]]"; Path=/
User-Is-Authenticated: True
Vary: Cookie,Accept-Encoding
Was it helpful?

Solution 2

Thanks for all the help and input -- I finally solved it. I don't know the exact root cause of why logging in with Facebook munged the cookies and standard login worked fine. I did notice that the domain of the cookies returned from the Facebook login were formatted with a leading "." like this:

[ .domain.com ]

Whereas the standard login that worked had cookie domains like this:

[ www.domain.com ]

I parsed the cookies from the HTTP response after successfully logging in with Facebook and stored them in the singleton:

                // Extract cookie information
                NSRange range = [cookieString rangeOfString:@"csrftoken="];
                if (range.location!=NSNotFound){
                    cookieString = [cookieString substringFromIndex:NSMaxRange(range)];
                    range = [cookieString rangeOfString:@";"];
                    if (range.location!=NSNotFound){
                        self.appDelegate.djangoCsrftoken = [cookieString substringToIndex:range.location];
                    }
                }
                range = [cookieString rangeOfString:@"sessionid="];
                if (range.location!=NSNotFound){
                    cookieString = [cookieString substringFromIndex:NSMaxRange(range)];
                    range = [cookieString rangeOfString:@";"];
                    if (range.location!=NSNotFound){
                        self.appDelegate.djangoSessionId = [cookieString substringToIndex:range.location];
                    }
                }

                if (LOGIN_DEBUG) { // Debug the response
                    NSLog(@"Extracted csrftoken is: %@",self.appDelegate.djangoCsrftoken);
                    NSLog(@"Extracted sessionid is: %@",self.appDelegate.djangoSessionId);
                }

I then, created those cookies explicitly for the following request:

    // Clear all cookies when app launches
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *each in cookieStorage.cookies) {
        //if ( [each.domain isEqualToString:DOMAIN] ) {
        NSLog(@"Deleting cookie: %@ -- %@",each.name,each.domain);
        [cookieStorage deleteCookie:each];
        //}
    }

    //////////////// CSRF TOKEN /////////////////////

    // Create cookies based on parsed values
    NSMutableDictionary *cookieCsrfProperties = [NSMutableDictionary dictionary];
    [cookieCsrfProperties setObject:@"csrftoken" forKey:NSHTTPCookieName];
    [cookieCsrfProperties setObject:self.appDelegate.djangoCsrftoken forKey:NSHTTPCookieValue];
    [cookieCsrfProperties setObject:DOMAIN forKey:NSHTTPCookieDomain];
    [cookieCsrfProperties setObject:DOMAIN forKey:NSHTTPCookieOriginURL];
    [cookieCsrfProperties setObject:@"/" forKey:NSHTTPCookiePath];
    [cookieCsrfProperties setObject:@"0" forKey:NSHTTPCookieVersion];

    // Set expiration to one month from now or any NSDate of your choosing
    // this makes the cookie sessionless and it will persist across web sessions and app launches
    /// if you want the cookie to be destroyed when your app exits, don't set this
    [cookieCsrfProperties setObject:[[NSDate date] dateByAddingTimeInterval:2629743] forKey:NSHTTPCookieExpires];

    NSHTTPCookie *csrfCookie = [NSHTTPCookie cookieWithProperties:cookieCsrfProperties];
    [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:csrfCookie];

    //////////////// SessionId TOKEN /////////////////////

    // Create cookies based on parsed values
    NSMutableDictionary *cookieSessionIdProperties = [NSMutableDictionary dictionary];
    [cookieSessionIdProperties setObject:@"sessionid" forKey:NSHTTPCookieName];
    [cookieSessionIdProperties setObject:self.appDelegate.djangoSessionId forKey:NSHTTPCookieValue];
    [cookieSessionIdProperties setObject:DOMAIN forKey:NSHTTPCookieDomain];
    [cookieSessionIdProperties setObject:DOMAIN forKey:NSHTTPCookieOriginURL];
    [cookieSessionIdProperties setObject:@"/" forKey:NSHTTPCookiePath];
    [cookieSessionIdProperties setObject:@"0" forKey:NSHTTPCookieVersion];

    // Set expiration to one month from now or any NSDate of your choosing
    // this makes the cookie sessionless and it will persist across web sessions and app launches
    /// if you want the cookie to be destroyed when your app exits, don't set this
    [cookieCsrfProperties setObject:[[NSDate date] dateByAddingTimeInterval:2629743] forKey:NSHTTPCookieExpires];

    NSHTTPCookie *sessionIdCookie = [NSHTTPCookie cookieWithProperties:cookieSessionIdProperties];
    [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:sessionIdCookie];

    ///////////////////////////////////////////////////

    // Create request
    NSURL *url = [NSURL URLWithString:requestUrl];
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
    urlRequest.HTTPShouldHandleCookies = YES;

    NSHTTPCookie *setCookie;
    for (setCookie in [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies) {
        if ( ([setCookie.name isEqualToString:@"csrftoken" ] || [setCookie.name isEqualToString:@"sessionid"]) ) {
            NSLog(@"Adding Cookie: %@ = %@  [ %@ ]", setCookie.name, setCookie.value, setCookie.domain);
            [urlRequest addValue:setCookie.value forHTTPHeaderField:setCookie.name];
        }
    }
    NSURLResponse *response = nil;
    NSError * error = nil;
    NSData *responseData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error];

After doing that, I could successfully login with Facebook using Django-allauth.

OTHER TIPS

I have run into a very similar problem as yours, in implementing Facebook login from an iOS app to a server running django-allauth. I noticed in the successful POST response in iOS that the sessionid cookie was not being automatically saved as it normally is. I believe that's the reason your subsequent calls are being denied and redirected to your main page.

Adding the following line seemed to solve it for me, but I admit that I do not have a complete understanding of why it works. Something to do with refreshing the session key, perhaps. Anyway, since there were no other answers, thought this might be helpful for you to try:

user = User.objects.get(email=email) # Get User
# Login the user from Django's perspective
user.backend = 'django.contrib.auth.backends.ModelBackend'
auth_login(request,user)
request.session.cycle_key() #Refresh session key

Then, on the iOS app side, I check whether there exists a session cookie:

NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString:WEB_APP_BASE_URL]];
for (NSHTTPCookie *cookie in cookies)
{
    if ([cookie.name isEqualToString:@"sessionid"]) {
        NSLog(@"found session cookie: %@",cookie.value);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top