web-dev-qa-db-fra.com

AngularJS + Django Rest Framework + CORS (Cookie CSRF n'apparaissant pas dans le client)

Je développe une application d'une page dans AngularJS en utilisant et Django Rest Framework + Django Headers CORS.

Mon problème est que le cookie "csrftoken" n'apparaît jamais dans mon navigateur lorsque j'ai contacté le backend.

Par exemple: je fais une connexion en utilisant un message. J'obtiens correctement le cookie "sessionid" mais le "csrftoken" n'apparaît jamais et je ne peux donc pas faire de publications correctes de mon client car je serai refusé en raison de l'absence du jeton csrf.

  • J'ai analysé les en-têtes de réponse de l'API et le csrftoken n'y est pas.
  • J'ai regardé directement dans le navigateur de l'API de repos et cela se présente bien ici.
  • Juste pour souligner, je peux faire mon premier POST pour me connecter depuis Django Rest Framework force uniquement CSRF pour les utilisateurs authentifiés. Si j'essaie de me reconnecter, il échouera) depuis le "sessionid" -cookie il présente.
  • Je ne suis pas intéressé à contourner la protection CSRF comme le suggèrent certains messages sur stackoverflow.

Quelques extraits de code du front/backend. Ce sont des extraits inachevés, alors ne vous attardez pas sur du code mal écrit.

Connexion à l'API du backend

class LoginView(APIView):

renderer_classes = (JSONPRenderer, JSONRenderer)

def post(self, request, format=None):
    serializer = LoginSerializer(data=request.DATA)

    if serializer.is_valid():
        userAuth = authenticate(username=serializer.data['username'], password=serializer.data['password'])

        if userAuth:

            if userAuth.is_active:
                login(request, userAuth)

                loggedInUser = AuthUserProfile.objects.get(pk=1)
                serializer = UserProfileSerializer(loggedInUser)

                user = [serializer.data, {'isLogged': True}]



        else:
            user = {'isLogged': False}

        return Response(user, status=status.HTTP_200_OK)

    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Contrôleur de connexion AngularJS côté client

.controller('LoginCtrl', ['$scope', '$http', 'uService', '$rootScope', function(scope, $http, User, rootScope) {

scope.login = function() {

    var config = {
        method: 'POST',
        withCredentials: true,
        url: rootScope.apiURL+'/user/login/',
        data : scope.loginForm
    };

    $http(config)
    .success(function(data, status, headers, config) {

        if (status == 200) {
            console.log(data[0]); //Test code
            // succefull login
            User.isLogged = true;
            User.username = data.username;

        }
        else {
            console.log(data); //Test code
            User.isLogged = false;
            User.username = '';
        }

    })
    .error(function(data, status, headers, config) {
        console.log('Testing console error');
        User.isLogged = false;
        User.username = '';
    });
};

}]);

Quelqu'un avec de bons conseils/idées/exemples?

33
JoakimB

J'ai donc trouvé ma propre solution à cela, semble fonctionner très bien.

Voici les nouveaux extraits de mon code:

API Backend LoginView (ajout d'un décorateur forçant l'ajout du jeton csrf au corps)

class LoginView(APIView):

renderer_classes = (JSONPRenderer, JSONRenderer)

@method_decorator(ensure_csrf_cookie)
def post(self, request, format=None):
    c = {}
    c.update(csrf(request))
    serializer = LoginSerializer(data=request.DATA)

    if serializer.is_valid():
        userAuth = authenticate(username=serializer.data['username'], password=serializer.data['password'])

        if userAuth:

            if userAuth.is_active:
                login(request, userAuth)

                loggedInUser = AuthUserProfile.objects.get(pk=1)
                serializer = UserProfileSerializer(loggedInUser)

                user = [serializer.data, {'isLogged': True}]



        else:
            user = {'isLogged': False}

        return Response(user, status=status.HTTP_200_OK)

    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Côté client AngularJS (ajouter un jeton à l'en-tête de la demande)

$http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;

Fichier de paramètres côté serveur (spécifiquement pour les en-têtes Django-cors)

Les 5 premiers sont ajoutés par défaut, mais vous devez ajouter "X-CSRFToken" pour autoriser un tel en-tête du client à l'API en utilisant CORS, sinon la publication sera refusée.

CORS_ALLOW_HEADERS = (
'x-requested-with',
'content-type',
'accept',
'Origin',
'authorization',
'X-CSRFToken'

)

C'est ça!

7
JoakimB

Application Web AngularJS à page unique sur le sous-domaine A, parlant à une Django API JSON (REST) ​​sur le sous-domaine B à l'aide de la protection CORS et CSRF

Comme je travaille actuellement sur une configuration similaire et que je luttais pour que CORS fonctionne correctement en combinaison avec la protection CSRF, je voulais partager mes propres apprentissages ici.

Setup - Le SPA et l'API sont tous deux sur des sous-domaines différents du même domaine:

  • AngularJS (1.2.14) Application Web à page unique sur le sous-domaine app.mydomain.com
  • Django App (1.6.2) implémente une API JSON REST sur le sous-domaine api.mydomain.com

L'application AngularJS est servie via une application Django dans le même projet que l'API Django API) de telle sorte qu'elle définit un cookie CSRF. Voir, par exemple, également Comment exécuter plusieurs sites Web à partir d'un seul Django

Django API App - Pour que la protection CORS et CSRF fonctionne, je devais faire ce qui suit sur le backend de l'API.

Dans settings.py pour cette application (une extension du projet Django settings.py):

  • Ajoutez l'application et le middleware corsheaders et le middleware CSRF:
INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

MIDDLEWARE_CLASSES = (
    ...
    'Django.middleware.csrf.CsrfViewMiddleware',
    ...
    'corsheaders.middleware.CorsMiddleware',
)

Voir aussi en-têtes Django CORS sur GitHub

  • Ajoutez le domaine de la SPA Webapp à la CORS_Origin_WHITELIST
CORS_Origin_WHITELIST = [
    ...
    'app.mydomain.com',
    ...
]
  • Définissez CORS_ALLOW_CREDENTIALS sur True. Ceci est important, si vous ne le faites pas, aucun cookie CSRF ne sera envoyé avec la demande

CORS_ALLOW_CREDENTIALS = True

Ajoutez le décorateur Ensure_csrf_cookie à vos vues en gérant les demandes de l'API JSON:

from Django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie
def myResource(request):
    ...

Application Django pour AngularJS - L'application AngularJS est servie via une application Django dans le même projet. Cette application Django est configuration pour définir un cookie CSRF. Le jeton CSRF du cookie est ensuite utilisé pour les requêtes vers l'API (qui s'exécute donc dans le cadre du même Django).

Notez que presque tous les fichiers liés à l'application AngularJS ne sont que des fichiers statiques de la perspective Django. L'application Django ne doit servir que le fichier index.html pour définir le cookie.

Dans settings.py pour cette application (encore une fois une extension du Django project settings.py), définissez CSRF_COOKIE_DOMAIN de telle sorte que les sous-domaines puissent également les utiliser:

CSRF_COOKIE_DOMAIN = ".mydomain.com"

Dans views.py, je n'ai besoin que de rendre le fichier AngularJS index.html, à nouveau en utilisant le décorateur Ensure_csrf_cookie:

from Django.shortcuts import render
from Django.views.decorators.csrf import ensure_csrf_cookie

# Create your views here.
@ensure_csrf_cookie
def index(request):
    return render(request, 'index.html')

Envoi de demandes à l'API en utilisant AngularJS - Dans la configuration de l'application AngularJS, définissez les valeurs par défaut $ httpProvider suivantes:

$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
$httpProvider.defaults.withCredentials = true;

Encore une fois, prenez note des withCredentials, cela garantit que le cookie CSRF est utilisé dans la demande.

Ci-dessous, je montre comment vous pouvez faire des demandes à l'API en utilisant le service AngularJS $ http et JQuery:

$http.post("http://api.mydomain.com/myresource", {
    field1   : ...,
      ...
    fieldN   : ...
}, {
    headers : {
        "x-csrftoken" : $cookies.csrftoken
    }
});

Voir aussi module ngCookies .

Utilisation de JQuery (1.11.0):

$.ajax("http://api.mydomain.com/myresource", {
    type: 'POST',
    dataType : 'json',
    beforeSend : function(jqXHR, settings) {
        jqXHR.setRequestHeader("x-csrftoken", get_the_csrf_token_from_cookie());
    },
    cache : false,
    contentType   : "application/json; charset=UTF-8",
    data : JSON.stringify({
        field1   : ...,
          ...
        fieldN   : ...
    }),
    xhrFields: {
        withCredentials: true
    }
});

J'espère que ça aide!!

35
Visionscaper

Directement à partir des documents https://docs.djangoproject.com/en/1.9/ref/csrf/#ajax

Si votre vue ne rend pas un modèle contenant la balise de modèle csrf_token, Django peut ne pas définir le cookie de jeton CSRF. Cela est courant dans les cas où des formulaires sont ajoutés dynamiquement à la page. Pour résoudre ce cas , Django fournit un décorateur de vue qui force la configuration du cookie: assure_csrf_cookie ().

Étant donné que votre application est une application à page unique, vous pouvez ajouter ensure_csrf_cookie() à la vue responsable du chargement initial de la page.

15
Derek Kwok

Une petite mise à jour de cette solution.

Depuis AngularJS 1.2.10, vous devez définir le cookie CSRF pour chaque type de demande dans le client:

$http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
$http.defaults.headers.put['X-CSRFToken'] = $cookies.csrftoken;
$http.defaults.headers['delete']['X-CSRFToken'] = $cookies.csrftoken;

Cela est dû au changement suivant qui s'est produit entre 1.2.9 et 1.2.10 https://github.com/cironunes/angular.js/commit/781287473bc2e8ee67078c05b76242124dd43376

J'espère que cela aide quelqu'un!

6
JoakimB

Après So Much Search, j'ai atterri sur cette solution et son travail me forme sur le système local et également sur le serveur de faction Web en direct, c'est ma solution pour Django les utilisateurs s'il vous plaît allez dans votre dossier Apache situé dans le projet puis dans le bac que vous trouvez

httpd.conf ou votre configuration de serveur pour php ou d'autres utilisateurs (généralement situés dans un fichier * .conf, tel que httpd.conf ou Apache.conf), ou dans un .htaccess. puis il suffit d'ajouter ce code

<IfModule mod_headers.c>
SetEnvIf Origin (.*) AccessControlAllowOrigin=$1
Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
Header set Access-Control-Allow-Credentials true
</IfModule> 

puis dans l'application js angulaire vous avez juste besoin de placer

angular.module('app', ['ngCookies'])
    .config([
   '$httpProvider',
   '$interpolateProvider',
   function($httpProvider, $interpolateProvider, $scope, $http) {
       $httpProvider.defaults.withCredentials = true;
       $httpProvider.defaults.xsrfCookieName = 'csrftoken';
       $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
   }]).
   run([
   '$http',
   '$cookies',
   function($http, $cookies) {
       $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
   }]);

Cela a fonctionné pour moi sur Django Angularjs platform.

https://Gist.github.com/mlynch/be92735ce4c547bd45f6

2
Akshay Kumbhar