Search code examples
reactjsdjangoaxiosgoogle-oauthcsrf

Django & React implementation using Google OAuth2 does not work with csrftoken


I have implemented Django backend for Google OAuth2 signin/signup process for user authorization.

The mechanism is triggered from React UI.

The user can successfully signup using Django backend and I can see the associated users are created in Django Admin.

However, when I try to use the same user information through React UI, "me" API call cannot access to the Django user that has signed in. But direct Django call through browser and curl command works fine.

The following command works fine with backend call :

curl -X GET --header "Cookie: csrftoken=M1kFFNataWZcbckfdrqUEiXuRRsSRwYKKCH4XvENUyWnLE9xnSMHe7DiaUcDBRU6; sessionid=p156z2d5gy9cwamojxvmxbopg84p99v6" http://localhost:8000/registration/me/

enter image description here

Below is Django settings for Cors and CSRF :

CORS_ORIGIN_ALLOW_ALL = True

ALLOWED_HOSTS = ['*']

CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000", 
]

CORS_ALLOW_ALL_ORIGINS=True

CORS_ALLOW_CREDENTIALS = False

CORS_ORIGIN_WHITELIST = [
    'http://localhost:3000'
]

CSRF_COOKIE_NAME = "csrftoken"
CSRF_COOKIE_HTTPONLY = False

CORS_EXPOSE_HEADERS = ["Content-Type", "X-CSRFToken"]
CORS_ALLOW_CREDENTIALS = True

CSRF_COOKIE_AGE = None
CSRF_COOKIE_DOMAIN = 'localhost'
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = True
CSRF_USE_SESSIONS = True

Below is rest framework settings :

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),

    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
    'DEFAULT_PERMISSIONS_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}

The API call from Django for "me" :

@api_view(['GET'])
def current_user(request):
    from rest_framework.permissions import IsAuthenticated
    permission_classes = [IsAuthenticated]
    user = request.user
    if user.is_authenticated:
        return Response({
            'username'       : user.username,
            'email'          : user.email,
            'firstname'      : user.first_name,
            'lastname'       : user.last_name,
            'picture'        : user.picture,
            'email_verified' : user.email_verified,
            'locale'         : user.locale,
            'token'          : user.token
        })

    return Response({'user' : 'anonymous'})

Below is the Axios call :

function getCookie(name) {
    var cookieValue = null;

    if (document.cookie && document.cookie !== '') {
        console.log("COOKIE : " + document.cookie);
        var cookieValue = document.cookie
    }
    return cookieValue;
}

var csrftoken = getCookie('csrftoken').split("=")[1];
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";

axios({
        method: 'get',
        url: 'http://localhost:8000/registration/me/',
        data: {
                csrfmiddlewaretoken: csrftoken,
                'Authentication' : "Token " + csrftoken,
              },
        headers: {
                "Content-Type": "X-CSRFToken",
                'X-CSRFTOKEN' : "M1kFFNataWZcbckfdrqUEiXuRRsSRwYKKCH4XvENUyWnLE9xnSMHe7DiaUcDBRU6",
                },
        withCredentials: false,
        xsrfHeaderName: 'X-CSRFToken',
        xsrfCookieName: 'csrftoken',
    }).then(res => {
        resultState.state = 'success';
        resultState.data = res.data;

        console.log("registration/me success : " + JSON.stringify(resultState.data));
    }).catch((err) => {
        resultState.state = 'error';
        resultState.data['message'] = err.message;

        console.log("registration/me error : " + JSON.stringify(resultState.data));
    })

The react response for the Django call is as below :

API_SETTINGS.JWT_AUTH_COOKIE : Cookie
API_SETTINGS.JWT_AUTH_COOKIE : 0:05:00
API_SETTINGS TOKEN : eyJ0eXAiOiJK...
API_SETTINGS.JWT_AUTH_COOKIE : Cookie
API_SETTINGS : <rest_framework.settings.APISettings object at 0x10e7f9300>
jwt_login : <HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="http://localhost:3000">
RESPONSE : <HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="http://localhost:3000">
[24/Jun/2022 21:02:27] "GET /api/v1/oauth/google/?code=4/0AX4XfWgL1FkFt3-21eIbY3MEi3WGqwkEBYcU4KXE5Rdw7LwwAG5__XgqVD4r0F6ApYpJVQ&scope=email%20profile%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile%20openid&authuser=3&hd=supereye.co.uk&prompt=none HTTP/1.1" 302 0
[24/Jun/2022 21:02:46] "GET /registration/me/ HTTP/1.1" 200 7613
[24/Jun/2022 21:02:47] "GET /static/rest_framework/css/bootstrap.min.css.map HTTP/1.1" 404 1746
[24/Jun/2022 21:07:56] "GET /registration/me/ HTTP/1.1" 200 20
[24/Jun/2022 21:07:56] "GET /registration/me/ HTTP/1.1" 200 20
registration/me success : {"user":"anonymous"}

React output for "me" call in Developer Console is below :

enter image description here

I can provide any further information regarding the setup. Any suggestions would be appreciated.


Solution

  • Actually, everything that is required was in place, only setting the parameter "withCredentials : true" was sufficient. The page already knew the cookie / associated user, but axios was not using the cookie information automatically.

    Adding parameter as below was sufficient :

    axios({
        method: 'get',
        url: 'http://localhost:8000/registration/me/',
        withCredentials: true,
    }).then(res => {
        resultState.state = 'success';
        resultState.data = res.data;
    }).catch((err) => {
        resultState.state = 'error';
        resultState.data['message'] = err.message;
    })