Django Angular Authentication CSRF cached template

558 Views Asked by At

I am getting status code 403 when I try to log in after successfully being logged in and logged out.

Client side is written in Angular and server side is in Django.

This goes as follows:

  1. Client requests url '/' fetches main HTML template with all required static files ( angular, bootstrap, jQuery sources and angular sources defined by me) with <div ng-view></div> tag into which further templates will be inserted.
  2. Via $location service is redirected to url '/#/login'
  3. This rule from $routeProvider is executed once '/#/login' is hit: $routeProvider.when('/login', { templateUrl: 'login.html' });
  4. 'login.html' is served by django view and form for logging in is rendered to the user
  5. User logs in successfully providing proper credentials
  6. Then user logs out, by clicking on a button, which fires '$http.get( '/logout/' );' and then is redirected to url '/#/login'
  7. Here is the problem. When user fills in credential form and sends 'POST' request, 403 is returned. I thought that it is, because this routing is done only by angular and since 'login.html' template has already been requested it has been catched and can be served without hitting backend, but after logging out currently possesed CSRF cookie is stale, so that's why I am getting 403. So I tried to remove that template:
logout: function(){

    var forceLoginTemplateRequest = function(){
        if( $templateCache.get('login.html') !== 'undefined'){
            $templateCache.remove('login.html');
        }
    };
    var responsePromise = $http.get(
        urls.logout
    );
    responsePromise.success(forceLoginTemplateRequest);
    return responsePromise;
}

After doing that I could see client side requesting 'login.html' template always after logging out, so I thought I could provide CSRF cookie when serving that template from backend:

#urls.py
urlpatterns = patterns(
    '',
    ...
    url(r'^$', serve_home_template),
    url(r'^login.html$', serve_login_template),
    url(r'^login/', login_view, name='login'),
    url(r'^logout/', logout_view, name='logout'),
    ...
)

#views.py
@ensure_csrf_cookie
def serve_login_template(request):
    return render(request, "login.html")

@ensure_csrf_cookie
def serve_home_template(request):
    return render(request, 'home.html')

But still it doesn't work and I am getting 403 when trying to log in after logging out. The only way I managed it to work is to simply refresh the page so that every single file, whether template or source file is requested again from the backend and CSRF cookie is updated with them.

Here is my app's run section for making sure CSRF cookie is sent with every request:

mainModule.run(['$http','$cookies', '$location', '$rootScope', 'AuthService', '$templateCache',
    function($http, $cookies, $location, $rootScope, AuthService, $templateCache) {
        $http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;

        $rootScope.$on( "$routeChangeStart", function(event, next, current) {
            if ( !(AuthService.isLoggedIn() == "true")){
                $location.path('/login');
            }

        });
}]);
3

There are 3 best solutions below

1
On

This could be a cache problem. Try to add the never_cache decorator to all your views:

from django.views.decorators.cache import never_cache

...

@ensure_csrf_cookie
@never_cache   
def serve_login_template(request):
    return render(request, "login.html")

...
0
On

I solved this problem by setting X-CSRFTOKEN header in $routeChangeStart event. I don't exactly know how module.run phase works, but it seems that when certain event defined within it occurs everything what is defined outside this event's handler body isn't executed.

mainModule.run(['$http','$cookies', '$location', '$rootScope', 'AuthService',
        function($http, $cookies, $location, $rootScope, AuthService) {
            $http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;

            $rootScope.$on( "$routeChangeStart", function(event, next, current) {

                // Added this line
                $http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;

                if ( !(AuthService.isLoggedIn() == "true")){
                    $location.path('/login');
                }

            });
}]);

This works together with removing 'login.html' template from $templateCache.

Instead of removing templates on client side with $templateCache service it is also possible to set your server to serve templates and set following headers:

Cache-Control: no-cache, no-store, must-revalidate
Pragma       : no-cache
Expires      : 0

Another way of dealing with this problem is to simply force page refresh, however I don't like this approach, since this is not pro-single-page-app approach. :)

0
On

One solution could be to read the current, fresh csrftoken directly from the cookie and then update the stale cookie using javascript.

var fresh_token = document.cookie.match('csrftoken=([a-zA-Z0-9]{32})