jazzband / django-oauth-toolkit

OAuth2 goodies for the Djangonauts!
https://django-oauth-toolkit.readthedocs.io
Other
3.13k stars 792 forks source link

How to get callback code of Django OAuth Toolkit use python #1324

Closed AakrutiPatel closed 11 months ago

AakrutiPatel commented 1 year ago

When open http://localhost/o/authorize/?response_type=code&code_challenge=xxx&code_challenge_method=S256&client_id=xxx&redirect_uri=http://localhost/callback, will be redirected to http://localhost/admin/login/?next=/o/authorize/%3Fresponse_type%3Dcode%26code_challenge%3Dxxx%26code_challenge_method%3DS256%26client_id%3Dxxx%26redirect_uri%3Dhttp%3A//localhost/callback, provide user email and password, and after successfully logging into account, will be redirected to http://localhost/callback?code=xxx.

I want get callback code in http://localhost/callback?code=xxx.

How can I redirect to callback url through the following code?

import os
import requests
from urllib.parse import urlparse, parse_qs

url = "http://localhost/admin/"
auth_url = 'http://localhost/o/authorize/?response_type=code&code_challenge=xxx&code_challenge_method=S256&client_id=xxx&redirect_uri=http://localhost/callback'

email = "email"
password = "password"

session = requests.Session()

session.get(url)

login_payload = {
    'email': email,
    'password': password,
    'csrfmiddlewaretoken': session.cookies['csrftoken']
}

login_req = session.post(url, data=login_payload)
print(login_req)
print(login_req.url)

auth_page = session.get(auth_url)
print(auth_page)
print(auth_page.url)

output:

<Response [200]>
http://localhost/admin/login/?next=/admin/
<Response [200]>
http://localhost/admin/login/?next=/o/authorize/%3Fresponse_type%3Dcode%26code_challenge%3Dxxx%26code_challenge_method%3DS256%26client_id%3Dxxx%26redirect_uri%3Dhttp%3A//localhost/callback

Can't get the callback url, like http://localhost/callback?code=xxx.

dopry commented 12 months ago

I'm not sure I follow what do you mean you can't get the callback url? What exactly are you trying to do?

AakrutiPatel commented 12 months ago

https://django-oauth-toolkit.readthedocs.io/en/latest/getting_started.html#authorization-code

Can't use requests to get http://localhost/callback?code=xxx, although "Skip Authorization" has been checked, but i can get http://localhost/callback?code=xxx using playwright.

dopry commented 12 months ago

I still don't understand what you're doing. Can you please write up steps to reproduce? For a point of reference, Lets use the sequence diagram at https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow/ The Auth0 Tenant would be your DOT instance, and the Regular Web App would be a Relying Party.

Generally you do not make an HTTP request to GET the redirect_uri, http://localhost/callback?code=xxx. In step 5 DOT will, after a successful login or if there is an existing session and skip authorization is true, redirect the browser back to the Relying Party/redirect_uri. The Relying party will need to parse the code from the query string and make a request to the token endpoint to get the credentials.

AakrutiPatel commented 12 months ago

Here's the code:

'''
pip3 install python-dotenv
pip3 install pytest-playwright
playwright install --with-deps chromium
'''

import base64
import hashlib
import os
import random
import string
import time
import urllib.parse
from dotenv import load_dotenv, set_key, get_key
from playwright.sync_api import sync_playwright

def run(playwright):
    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('=', '')

    browser = playwright.chromium.launch()
    context = browser.new_context()
    page = context.new_page()

    username = "xxx"
    password = "xxx"
    scope = "read+write"
    client_id = "xxx"
    redirect_uri = "http://localhost/callback"
    url = f"http://localhost/o/authorize/?response_type=code&scope={scope}&code_challenge={code_challenge}&code_challenge_method=S256&client_id={client_id}&redirect_uri={redirect_uri}"

    page.goto(url)

    page.fill('input[name="username"]', username)
    page.fill('input[name="password"]', password)
    page.click('input[type="submit"]')

    time.sleep(1)

    current_url = page.url
    parsed_url = urllib.parse.urlparse(current_url)
    params = urllib.parse.parse_qs(parsed_url.query)
    code = params.get('code', [None])[0]
    # print(f"Code: {code}")

    # https://github.com/theskumar/python-dotenv
    load_dotenv()
    set_key(".env", "CODE", code)
    # print(get_key(".env", "CODE"))

    browser.close()

with sync_playwright() as p:
    run(p)
dopry commented 11 months ago

It looks like you're trying to test an OAuth login flow with playwright.

What is being rendered in playwright after logging in? Does playwright automatically follow the redirect? What is rendering http://localhost/callback? Does the site at localhost/callback further redirect playwright based on the state tracked with the login? There are still a lot of blanks.

AakrutiPatel commented 11 months ago

http://localhost/callback is "Redirect uris" of application (see https://django-oauth-toolkit.readthedocs.io/en/latest/getting_started.html#authorization-code).

alt=''

Playwright open the value of url variable, and after 1 second of sleep, it will redirect to http://localhost/callback?code=xxx, then save the code xxx to .env file.

You can run this python code. Before running it, replace http://localhost with your domain(eg: http://127.0.0.1:8000), replace http://localhost/callback with your "Redirect uris" of application(eg: http://127.0.0.1:8000/noexist/callback), then change the value of username, password and client_id variable.

dopry commented 11 months ago

You should check that playwright is redirecting and look at where playwright ends up. It might have gotten an error, or maybe the code is lost when playwright displays a 404. This doesn't appear to be a bug in DOT, I have multiple instances that use this flow and DOT/OAuthlib have test coverage for these features, so I'm not going to setup a replication environment to help you fix your E2E testing setup.

AakrutiPatel commented 11 months ago

Need to use a browser-based library to get the code automatically(eg: Playwright, Selenium), and need to wait about 1 second for a response from the browser before getting the code. time.sleep(1) doesn't work with the requests library, it works fine when using Playwright or Selenium.

dopry commented 11 months ago

So you've figured it out?

AakrutiPatel commented 11 months ago

Yes. Playwright or Selenium can do more.