[Django]-How to cURL an Authenticated Django App?

53👍

Here is a fully coded answer. The idea of the solution is:

  1. you have to first visit the login page with GET to get the cookies file generated,
  2. then parse the CSRF token out of the cookies file
  3. and do the login using a POST request, passing the data with -d.

Afterwards you can perform any request always using that CSRF token in the data ($DJANGO_TOKEN) or with a custom X-CSRFToken header. To log out simply delete the cookies file.

Note that you need a referer (-e) to make Django’s CSRF checks happy.

LOGIN_URL=https://yourdjangowebsite.com/login/
YOUR_USER='username'
YOUR_PASS='password'
COOKIES=cookies.txt
CURL_BIN="curl -s -c $COOKIES -b $COOKIES -e $LOGIN_URL"

echo -n "Django Auth: get csrftoken ..."
$CURL_BIN $LOGIN_URL > /dev/null
DJANGO_TOKEN="csrfmiddlewaretoken=$(grep csrftoken $COOKIES | sed 's/^.*csrftoken\s*//')"

echo -n " perform login ..."
$CURL_BIN \
    -d "$DJANGO_TOKEN&username=$YOUR_USER&password=$YOUR_PASS" \
    -X POST $LOGIN_URL

echo -n " do something while logged in ..."
$CURL_BIN \
    -d "$DJANGO_TOKEN&..." \
    -X POST https://yourdjangowebsite.com/whatever/

echo " logout"
rm $COOKIES

I have a slightly more secure version of this code, which uses a file for submitting the POST data, as a Gist on GitHub: django-csrftoken-login-demo.bash

Interesting background reading on Django’s CSRF token is on docs.djangoproject.com.

3👍

Passing username:password in a curl request is only good for HTTP Authentication, which isn’t how most websites do auth these days. Instead, you’ll have to post to the login page, get the cookie, then pass it back when requesting your desired page.

2👍

Actually @Paterino answer is correct but it will not work on every implementation of sed. Instead sed 's/^.*csrftoken\s*//') we can use sed 's/^.*csrftoken[[:blank:]]*//') which is more old fashioned. MacOSXs curl doesn’t use escaping, so \n\t\s don’t work at all.

👤jinni

2👍

I’m using Django 4.1.2 and trying the @Paterino method found a couple of changes to make it work (but i have not enogh reputation to comment so wrote another answer).

Firstly, if the generated cookies.txt file is empty you have to ensure than csrf cookie is generated. I achieved this using django.views.decorators.csrf.ensure_csrf_cookie in django.contrib.auth.views.LoginView

Now, after login cookies.txt changes, so you have to recalculate DJANGO_TOKEN variable in the same way:

DJANGO_TOKEN="csrfmiddlewaretoken=$(grep csrftoken $COOKIES | sed 's/^.*csrftoken\s*//')"

From here the method doesn’t change.

1👍

To use the token with a get request, use

$CURL_BIN \
    -H "$DJANGO_TOKEN" \
    -X GET https://yourdjangowebsite.com/whatever/

I tried using -d with -X GET, however it resulted in weird socket behaviour on the server side (Heruko H18 errors).

0👍

the accepted answer, until now(2022-12-19), has 2 issues:

  1. misses updating DJANGO_TOKEN after login (since a new csrftoken cookie is returned after login)
  2. doesn’t include an example with a POST request (moving the csrftoken to a header) where -d already contains some payload

here is my version dealing with both:

# user and password from `./manage.py createsuperuser`
YOUR_USER='user'
YOUR_PASS='pass'

COOKIES=cookies.txt
LOGIN_URL=http://localhost:8000/admin/login/

# stores csrftoken cookie on cookies.txt
curl -s -c $COOKIES $LOGIN_URL > /dev/null

TOKEN_VALUE="$(grep -oP '(?<=csrftoken[[:space:]]).*' cookies.txt)" # https://stackoverflow.com/a/10358949/3026886 https://stackoverflow.com/a/4233691/3026886

# logs in, updating csrftoken and adding sessionid cookies
curl -b $COOKIES -c $COOKIES -d "csrfmiddlewaretoken=$TOKEN_VALUE&username=$YOUR_USER&password=$YOUR_PASS" $LOGIN_URL

# updates var env with new cookie
TOKEN_VALUE="$(grep -oP '(?<=csrftoken[[:space:]]).*' cookies.txt)"

# here comes the real request
curl -s -X POST -b $COOKIES -d "{\"a\":1}" -H "X-CSRFToken: $TOKEN_VALUE" http://localhost:8000/yourViewReceivingJsonPayload/ > /dev/null

rm cookies.txt
👤rado

0👍

To sum up from https://stackoverflow.com/a/24376188/14298786 and https://stackoverflow.com/a/74186787/14298786 , use the following shell script:

# Example using curl to log in with an account.

LOGIN_URL=http://127.0.0.1:8000/accounts/login/ # change it to yours
PROTECTED_URL=http://127.0.0.1:8000/problem/reference/ # change it to yours
YOUR_USER=xxxx # change it to yours
YOUR_PASS=xxxx # change it to yours
COOKIES=cookies.txt
CURL_BIN="curl -s -c $COOKIES -b $COOKIES -e $LOGIN_URL"

echo "Django Auth: get csrftoken..."
$CURL_BIN $LOGIN_URL > /dev/null
DJANGO_TOKEN="csrfmiddlewaretoken=$(grep csrftoken $COOKIES | sed 's/^.*csrftoken[[:blank:]]*//')"

echo "Perform login..."
$CURL_BIN \
    -d "$DJANGO_TOKEN&username=$YOUR_USER&password=$YOUR_PASS" \
    -X POST $LOGIN_URL

echo "Visit a url that only authenticated users could access..."
# Recalculate token: https://stackoverflow.com/a/74186787/14298786
DJANGO_TOKEN="csrfmiddlewaretoken=$(grep csrftoken $COOKIES | sed 's/^.*csrftoken[[:blank:]]*//')"
$CURL_BIN \
    -d "$DJANGO_TOKEN&..." \
    -X POST $PROTECTED_URL

echo "logout"
rm $COOKIES

ps. Your login view should be something like:

def LoginView(request):
    username = request.POST["username"]
    password = request.POST["password"]
    user = authenticate(request, username=username, password=password)
    if user is not None:
        login(request, user)
        return JsonResponse(data={"successfully": "logged in"})
    else:
        return JsonResponse(data={"access": "denied"})

Leave a comment