Search code examples
androiddjangodjango-usersdjango-csrfforgot-password

Django: resetting password without a CSRF token


I have a Django website that manages Users. Using the built-in functionality, users can request a password reset from the website and that works great. I have implemented it according to this tutorial so I am using the built-in password reset functionality.

I have an Android app from which users should also be able to request a password reset. The problem is that I do not have a CSRF token in the application, and the the built-in password_reset method has the @csrf_protect decorator. This means that I cannot access it without a CSRF token and I also can't modify it with the @csrf_exempt decorator.

So the next idea is to create a function, which generates a CSRF token, stores it in the request and redirects to the correct URL which sends the reset email. The problem is that according to this, django does not allow to pass POST parameters further in a redirect.

Therefore my question is how can I request a password reset in Django without a CSRF token? Alternatively, what is the correct way to request this from an application?


Solution

  • I found a solution myself. Please feel free to post any alternative solutions. One that doesn't require two separate requests would be particularly great.

    If you look at the password_reset method, you can see that it only tries to process the request as a reset request if the request method is POST. Otherwise it just returns a TemplateResponse containing a form. This also contains the CSRF token as a cookie.

    So first, I send a GET request to http://myaddress.com/user/password/reset/ and extract the CSRF cookie from the response. Then I send a POST request containing the cookie, the email address and 2 headers (see below).

    This is the code I've implemented to achieve this from Android (trimmed):

    String url = "http://myaddress.com/user/password/reset/";
    

    GET Request:

    HttpClient httpClient = new DefaultHttpClient();
    HttpGet httpGet = new HttpGet(url);
    CookieStore cookieStore = new BasicCookieStore();
    HttpContext localContext = new BasicHttpContext();
    localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
    HttpResponse httpResponse = httpClient.execute(httpGet, localContext);
    
    Cookie csrfCookie = null;
    for (Cookie cookie : cookieStore.getCookies()) {
        if (cookie.getName() == "csrftoken") {
            csrfCookie = cookie;
            break;
        }
    }
    
    if (csrfCookie == null) {
        throw new NullPointerException("CSRF cookie not found!");
    }
    
    return csrfCookie;
    

    Note that you want the CookieStore from org.apache.http.client.

    POST Request:

    HttpClient httpClient = new DefaultHttpClient();
    HttpContext localContext = new BasicHttpContext();
    HttpPost httpPost = new HttpPost(url);
    
    // Prepare the cookie store to receive cookies.
    CookieStore cookieStore = new BasicCookieStore();
    cookieStore.addCookie(csrfCookie);
    httpPost.setHeader("Referer", url);
    httpPost.setHeader("X-CSRFToken", csrfCookie.getValue());
    localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
    
    MultipartEntityBuilder builder = MultipartEntityBuilder.create();
    builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
    builder.addTextBody("email", emailAddressToReset);
    httpPost.setEntity(builder.build());
    
    HttpResponse httpResponse = httpClient.execute(httpPost, localContext);
    if (httpResponse.getStatusLine().getStatusCode() != 200) {
        throw new Exception("Could not reset password!");
    }
    
    Toast.makeText(context, "Password reset requested! Please check your email inbox!", Toast.LENGTH_LONG).show();