Search code examples
pythondjangodjango-oauth-toolkit

Getting invalid_grant when trying to exchange tokens using Django-oauth-toolkit


I am trying to use the django-oauth-toolkit but continually hitting an invalid_grant error.

I believe I have followed the instructions at https://django-oauth-toolkit.readthedocs.io/en/latest/getting_started.html#oauth2-authorization-grants to the letter. I have created the following script to test with (mostly just extracted the code mentioned in the above page:

import random
import string
import base64
import hashlib

code_verifier = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(random.randint(43, 128)))
code_verifier = base64.urlsafe_b64encode(code_verifier.encode('utf-8'))

code_challenge = hashlib.sha256(code_verifier).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8').replace('=', '')

client_id = input("Enter client id: ")
client_secret = input("Enter client secret: ")
redirect_url = input("Enter callback url: ")

print(f'vist http://127.0.0.1:8000/o/authorize/?response_type=code&code_challenge={code_challenge}&code_challenge_method=S256&client_id={client_id}&redirect_uri={redirect_url}')

code = input("Enter code: ")

print(f'enter: curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/token/" -d "client_id={client_id}" -d "client_secret={client_secret}" -d "code={code}" -d "code_verifier={code_verifier}" -d "redirect_uri={redirect_url}" -d "grant_type=authorization_code"')

The following shows the output (I haven't redacted the details, they are only temporary):

(venv) (base) peter@Peters-MacBook-Air Intra-Site % python api_test.py 
Enter client id: sJ3ijzbmdogfBZPfhkF6hOqifPuPSKKpOnN8hq1N
Enter client secret: GnXNWqw7t1OwPRbOWTmDXKiuaqeJ7LRMhY9g2CWe00f3QrHLvx6aDKjGf5eF1t6QPkD1YO8BR43HNmzCjZYBW81FIjTng7QnVypzshMljEJRGTj5N7r8giwjKIiXyVng
Enter callback url: https://192.168.1.44/authenticate/callback
vist http://127.0.0.1:8000/o/authorize/?response_type=code&code_challenge=WiaJdysQ6aFnmkaO8yztt9kPBGUj-aqZSFVgmWYRBlU&code_challenge_method=S256&client_id=sJ3ijzbmdogfBZPfhkF6hOqifPuPSKKpOnN8hq1N&redirect_uri=https://192.168.1.44/authenticate/callback
Enter code: hrLeADVEmQF8mDLKJXhAZJRQ2dElV7
enter: curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/token/" -d "client_id=sJ3ijzbmdogfBZPfhkF6hOqifPuPSKKpOnN8hq1N" -d "client_secret=GnXNWqw7t1OwPRbOWTmDXKiuaqeJ7LRMhY9g2CWe00f3QrHLvx6aDKjGf5eF1t6QPkD1YO8BR43HNmzCjZYBW81FIjTng7QnVypzshMljEJRGTj5N7r8giwjKIiXyVng" -d "code=hrLeADVEmQF8mDLKJXhAZJRQ2dElV7" -d "code_verifier=b'UDZGREwyR0ZVMjNCTzRBQlFaVlBUQk9TWkVRUDlCQzFSUTY1MkFNMUUzRTVCRVBNNlkwR0k4UEtGMUhaVVlTVkQ0QVowMzJUR0xLTDQ1U0VNOEtaWVcxT0tP'" -d "redirect_uri=https://192.168.1.44/authenticate/callback" -d "grant_type=authorization_code"
(venv) (base) peter@Peters-MacBook-Air Intra-Site % curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/token/" -d "client_id=sJ3ijzbmdogfBZPfhkF6hOqifPuPSKKpOnN8hq1N" -d "client_secret=GnXNWqw7t1OwPRbOWTmDXKiuaqeJ7LRMhY9g2CWe00f3QrHLvx6aDKjGf5eF1t6QPkD1YO8BR43HNmzCjZYBW81FIjTng7QnVypzshMljEJRGTj5N7r8giwjKIiXyVng" -d "code=hrLeADVEmQF8mDLKJXhAZJRQ2dElV7" -d "code_verifier=b'UDZGREwyR0ZVMjNCTzRBQlFaVlBUQk9TWkVRUDlCQzFSUTY1MkFNMUUzRTVCRVBNNlkwR0k4UEtGMUhaVVlTVkQ0QVowMzJUR0xLTDQ1U0VNOEtaWVcxT0tP'" -d "redirect_uri=https://192.168.1.44/authenticate/callback" -d "grant_type=authorization_code"
{"error": "invalid_grant"}%

I have the following in my settings.py:

OAUTH2_PROVIDER = {
    'ALLOWED_REDIRECT_URI_SCHEMES': ["https", "intra"],
    "SCOPES": {
        "read": "Read scope",
        "write": "Write scope",
        "groups": "Access to your groups",
    },
}

REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "oauth2_provider.contrib.rest_framework.OAuth2Authentication",
    ],
}

I also have the following in my installed apps:

    "oauth2_provider",
    "rest_framework",

It is most likely something mundane I have missed but cannot identify what.

Can anyone shed any light on why I might be getting the grant_type error.

I have confirmed that the redirect URL, client id, client secret are correct as they are in the application registered in dJango

I have also ensured that the code verifier and code challenge are correct and appear to be sent at the right times.


Solution

  • After testing in Postman and this being able to create a token I managed to rule out a setup issue. After playing around with the script I have identified that the instructions are wrong in the documentation.

    The following:

    code_verifier = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(random.randint(43, 128)))
    code_verifier = base64.urlsafe_b64encode(code_verifier.encode('utf-8'))
    
    code_challenge = hashlib.sha256(code_verifier).digest()
    code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8').replace('=', '')
    

    needs to be replaced with:

    code_verifier = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(random.randint(43, 128)))
    
    code_challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest()
    code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8').replace('=', '')