24

I've a few APIs I'd like to test with cURL. I tried doing a GET as follows:

curl --user username:password --request GET http://my_domain/get_result/52d6428f3ea9a008358ad2d8/

On the server, it showed a '302' (which means redirection, right?). I'm guessing it redirected to the 'login/' page.

What is the proper way of getting this done?

Edit: I tried:

curl -c cookies.txt -b cookies.txt -L -d @login_form.txt http://my_domain/login/

where login_form.txt contains "username=username&password=password&this_is_the_login_form=1". Doesn't work. No cookies.txt files generated. And no login happening. Can you tell me how you achieve login to Django using cURL?

Sreejith Ramakrishnan
  • 1,332
  • 2
  • 13
  • 22

7 Answers7

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.

Peterino
  • 15,097
  • 3
  • 28
  • 29
  • 1
    I have to say this saved my day. Althought it is working fine without referer for now.. – darxsys Nov 28 '14 at 15:43
  • 3
    " sed 's/^.*csrftoken\s*//' " was putting an extra space before the csrf value. That's why, I used " awk {'print $7'} " instead. – VolkanT Mar 24 '15 at 12:29
  • On macOS I ended up with an extra tab in front of the token—so I replaced `\s` with `[[:space:]]` as recommended in [this super user post](https://superuser.com/questions/112834/how-to-match-whitespace-in-sed#). Also, I had to modify the login URL because I was getting 302s. – whlteXbread Jun 05 '17 at 06:21
  • 1
    On MacOs, and using `sed 's/^.*csrftoken[[:blank:]]*//')` worked for me. Now I'm getting `CSRF Failed: CSRF token missing or incorrect.`. Any ideas how to fix? – Tomiwa Mar 13 '21 at 18:19
  • This doesn't work if you include `Content-Type: application/json` in the header. – default123 Jul 29 '21 at 19:02
  • I'm able to generate a cookies.txt file, but it's empty except for comments from libcurl. – Alexej Magura Dec 07 '21 at 19:33
  • i've added some differences over this answer: https://stackoverflow.com/a/74846245/3026886 – rado Dec 19 '22 at 04:18
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.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
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
  • 101
  • 1
  • 3
  • Once you have sufficient [reputation](http://stackoverflow.com/help/whats-reputation) you will be able to [comment](http://stackoverflow.com/help/privileges/comment) on any post. Also check this [what can I do instead](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). – thewaywewere Apr 24 '17 at 10:00
  • 1
    Thanks for pointing this out. The answer is about the general approach, the implementation details refer to the GNU version of sed (as of Linux) and is just an example. I have a Gist to make GNU sed the default also on Macs: https://gist.github.com/bittner/5436f3dc011d43ab7551 – Peterino Jun 05 '17 at 09:31
  • Nice looks like perfect solution, I will try that later. My post is more or less extension for your answer and for other people that could struggle like me with it. – jinni Jun 12 '17 at 14:02
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).

Dijkgraaf
  • 11,049
  • 17
  • 42
  • 54
mikkelhn
  • 11
  • 1
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
  • 5,720
  • 5
  • 29
  • 51
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"})
Yang_____
  • 117
  • 8