Tivix / django-rest-auth

This app makes it extremely easy to build Django powered SPA's (Single Page App) or Mobile apps exposing all registration and authentication related functionality as CBV's (Class Base View) and REST (JSON)
www.tivix.com
MIT License
2.4k stars 663 forks source link

Can't login with email - CSRF error #159

Open LeonardoGentile opened 8 years ago

LeonardoGentile commented 8 years ago

I am using django 1.9 trying to login with email by the help of a rest client (non-browser, so no cookies) for sending this credentials as post to /rest-auth/login/

{
    "email" : "whatever@world.com",
    "password": "hello"
}

For doing so I had to add this block in settings accoring to the allauth docs:

AUTHENTICATION_BACKENDS = (
    # `allauth` specific authentication methods, such as login by e-mail
    'allauth.account.auth_backends.AuthenticationBackend',
)

And these settings:

ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email' # (="username" | "email" | "username_email)
ACCOUNT_EMAIL_VERIFICATION = 'optional'

Now everytime I post I get back

{"detail":"CSRF Failed: CSRF token missing or incorrect."}

The only two ways to get rid of the error are 1 Get rid of this line 'allauth.account.auth_backends.AuthenticationBackend', or get removing the tuple AUTHENTICATION_BACKENDS altogether. The problem is that by doing so I won't be able to login with email anymore.

2 Comment the Rest Session Authentication setting:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        # 'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    )
}

I wonder if this line was meant only for the demo purposes or is it necessary in case of pure REST usage?

-> What are the correct settings for email login using pure REST calls?

LeonardoGentile commented 8 years ago

Even changing the order

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    )
}

so that we can preserve the browsable API interface and the admin login won't help. I think the problem is described in this issue

LeonardoGentile commented 8 years ago

My fix:

# myapp.urls

from views import LoginViewCustom

urlpatterns = [
    # NOTE: first defined first served order
    url(r'^rest-auth/login/$', LoginViewCustom.as_view(), name='rest_login'),
    url(r'^rest-auth/', include('rest_auth.urls')),
]
# myapp.views.py

from rest_framework.authentication import TokenAuthentication
from rest_auth.registration.views import LoginView

class LoginViewCustom(LoginView):
    authentication_classes = (TokenAuthentication,)

To the creators, if this is a real fix I will then provided a PR

Akay7 commented 8 years ago

I though that is not trouble of django-rest-auth. You get CSRF error because forget setup way for authenticate.

And if you use SessionAuthentication you must send CSRF token with every request.

LeonardoGentile commented 8 years ago

@Akay7 the authentication backend are supposed to be parsed in sequence, so by setting this

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    )
}

should first try to authenticate with Token then using Session but it seems it doesn't work for unknown reasons. I want to preserve both Token ans Session because I want to use the browsable api (it uses session) but I don't want to setup a csrf token for rest authentication.

My solution works tough.

Akay7 commented 8 years ago

@LeonardoGentile Yep, your solution will work. But that not about authentification, that about CSRF token, you always need send it with POST requests if you use SessionAuthentication(You also can override enforce_csrf function in SessionAuthentication module)

If you sure about you don't need Cross-Site Request Forgery security option on login view and use SessionAuthentication only on django-admin page, your code will works well.

But anyway that is not issue of this project. That issue just about not correct setup of project. I'm also know if any other view of your project will don't direct setup authentication_classes to TokenAuthentication, it will crush on POST(DELETE, PUT, PATCH) request.

andres-torres-marroquin commented 8 years ago

I had this problem on a Cordova app, my solution was to disable the REST_SESSION_LOGIN, but I'm not quite sure that this is a problem on django-rest-auth, but maybe my solution and @LeonardoGentile's solution should be documented somewhere into the docs.

Akay7 commented 8 years ago

@andres-torres-marroquin I guess everyone who use SessionAuth + Token auth get same error, and know right solution for that. But documented this is nice idea.

divick commented 8 years ago

@Akay7 I can't understand what you write. Could you please write in proper english.

Akay7 commented 8 years ago

@divkis01 I'm sorry, but I can't help to you

divick commented 8 years ago

@Akay7 No problem. The solution by @LeonardoGentile works. I just pointed out because reading through this issue, your comments make it illegible to read.

georoot commented 7 years ago

Hi i am using django-rest-auth==0.9.0 and still not able to login with email and password with following configuration.

#ACCOUNT_EMAIL_REQUIRED=True
#ACCOUNT_AUTHENTICATION_METHOD="email"
#ACCOUNT_USERNAME_REQUIRED=False
#ACCOUNT_EMAIL_VERIFICATION="none"
#ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE=False
#ACCOUNT_LOGOUT_ON_GET = True

Am i missing something out or is this issue resolved in some separate version. The response i am getting is

{
    "non_field_errors": [
        "Unable to log in with provided credentials."
    ]
}
Akay7 commented 7 years ago

@georoot Usually you get this error, when input wrong email password

georoot commented 7 years ago

@Akay7 i tried it multiple times. That was the first thing that came to my mind. After the number of attempts put in i am pretty sure that is not the problem.One thing that i did notice was that the username was being generated at random, not sure but can that be somehow the reason ? It works fine when i use username and password. Only has this problem with emails

Akay7 commented 7 years ago

@georoot Please show variables INSTALLED_APPS and 'DEFAULT_AUTHENTICATION_CLASSES' of REST_FRAMEWORK from settings. And I would like to look at payload of post request which you send to login api.

ibqn commented 7 years ago

Nice, @LeonardoGentile .

So, what is the state here? is it more a configuration issue or a bug?

jasrusable commented 7 years ago

@georoot @Akay7 I am having the same issue.

Authenticating with email + password with ACCOUNT_AUTHENTICATION_METHOD = 'email' results in:

{
    "non_field_errors": [
        "Unable to log in with provided credentials."
    ]
}

Authenticatting with username + password with ACCOUNT_AUTHENTICATION_METHOD = 'username' works fine.

settings.py

"""
Django settings for mbs project.

Generated by 'django-admin startproject' using Django 1.11.1.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'znvwm+-!ej)_)rm!8jj+yvfe)kp2*w2ejll1%(4*b@4v6p4^h9'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    'mbs_app.apps.MbsAppConfig',
    'rest_framework',
    'rest_framework.authtoken',
    'rest_auth',
    'rest_auth.registration',
    'allauth',
    'allauth.account',
    'django_filters',
    'corsheaders',
    'django.contrib.sites',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'mbs.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'mbs.wsgi.application'

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'HOST': os.environ['DB_HOST'],
        'USER': os.environ['DB_USER'],
        'NAME': os.environ['DB_NAME'],
    }
}

# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
        'rest_framework.filters.OrderingFilter',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ),
    'DEFAULT_PAGINATION_CLASS': 'mbs_app.stuff.pagination.CustomPagination',
    'PAGE_SIZE': 30,
}

# For django-rest-auth
SITE_ID = 1
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_VERIFICATION = 'optional'

CORS_ORIGIN_ALLOW_ALL = True

Request payload:

{"password":"my_password","email":"joe@example.com"}

Response:

{"non_field_errors":["Unable to log in with provided credentials."]}
jasrusable commented 7 years ago

Solved https://github.com/Tivix/django-rest-auth/issues/159#issuecomment-302893368 by adding

AUTHENTICATION_BACKENDS = (
    # `allauth` specific authentication methods, such as login by e-mail
    'allauth.account.auth_backends.AuthenticationBackend',
)

to settings.py

Idanraman commented 6 years ago

Went through the exact same proccess, its a shame this solution isn't implemented on the project / gets presented on the docs.

pravin-d commented 6 years ago

The above solution doesn't seem to be working for me. Does order of "'allauth.account.auth_backends.AuthenticationBackend'" matter ?

SHxKM commented 5 years ago

@LeonardoGentile - hey, I'm having the same issue as you are. I think your subclassing of the login view is a neat solution, but did you also have to create it for other auth-related views?

One thought I had is to get rid of 'rest_framework.authentication.SessionAuthentication', I can live without the browserable API interface and I don't think I need it, but then there's also the issue of CSRF, which I'm not entirely sure about.

jqsjqs commented 4 years ago

@SHxKM CSRF attacks happen because the browser automatically sends (in this case) session cookies to the server. There are a variety of ways a bad guy can trick your browser into sending those cookies and trying to ride along and perform an action, but they can't see inside the cookie. The CSRF cookie/token combo foils this basically by providing a CSRF cookie and having you manually POST the CSRF token inside that cookie back to the server either via an HTTP header or as part of the POST data. Django's {% csrftoken %} directive just prefills it into the POST data in your template for convenience. The server then compares the CSRF cookie automatically sent by the browser and what you manually provided (header or POST data) to see if they're the same. All of this is only needed if the browser is somehow automatically sending an authentication id to the server (like a session id). If you turn off SessionAuthentication, then there's no session cookie for the browser to automatically send to the server and thus no CSRF concern.

TokenAuthentication requires you to set a Token authentication header on the HTTP POST request - it isn't set automatically by the browser, which is why token-based authentication is safe from CSRF.

DrChai commented 4 years ago

since social_serializers.py uses complete_social_login(request, login) from original allauth library. during this process. it will eventually trigger django's login() to perform typical session-based login. So no matter if you set REST_SESSION_LOGIN or not, seesion_id and csrf would be added the header.

Rouizi commented 4 years ago

you need to add these backends to settings.py file:

AUTHENTICATION_BACKENDS = (
   "django.contrib.auth.backends.ModelBackend",
   "allauth.account.auth_backends.AuthenticationBackend"
)
leogregianin commented 4 years ago

Works for me with this: https://github.com/Tivix/django-rest-auth/issues/164#issuecomment-623804744

AtitBimali commented 1 year ago

And if you use SessionAuthentication you must send CSRF token with every request.

I disabled session authentication, now it's working well.