vitalik / django-ninja

💨 Fast, Async-ready, Openapi, type hints based framework for building APIs
https://django-ninja.dev
MIT License
6.9k stars 418 forks source link

Trivial question about external requests #1237

Open domcyi24 opened 1 month ago

domcyi24 commented 1 month ago

Hi there.

This is probably a trivial question, still hope you can help me and my project getting back on track. One of many functions I have inside my main/api.py is called whoamI

#Initiate the instance
apiR = NinjaAPI(csrf=True, title="Dept. of XXXXXXXXXXX  :: API endpoint", 
                 description="API functions to access, download and query data from Dept. of XXXXXXXXX",
                 auth=django_auth
                 )

and should serve for self-assessing the role of each logged user:


@apiR.get("/whoamI" )
def whoamI(request, username: str, password: str):
    user = authenticate(username=username, password=password)
    if user is not None:

        dict_out = {}
        dict_out['authentication'] = 'success'
        dict_out['username'] = user.username
        dict_out['email'] = user.email
        dict_out['first_name'] = user.first_name
        dict_out['last_name'] = user.last_name
        dict_out['date_joined'] = user.date_joined
        dict_out['last_login'] = user.last_login
        return dict_out

    else:
        raise HttpError(503, "Login failed due to wrong combination of username + password")

The thing works allright when I am inside an activate django session. However, if I am trying to submit a request, for instance via python, I get either 401 or 405 error message. The function above, like many others inside my api.py, should ALLOW user having a valid passord and username, to submit a request programmatically and getting, in return, a response.

That response should be, for example:

Now, again, when inside the running django session all works fine. When, instead, I open an incognito window, or submit a request through python, things get ugly.

Why? and How to solve?

Thanks!

vitalik commented 1 month ago

1) hard to tell - you need to show your python client code as well

2) user = authenticate(username=username, password=password) - is not enogh if you want to use django session - you need as well call login(request, user) to store it in session - documentation

domcyi24 commented 1 month ago

Thanks for your kind response. I got it working with Bearer, by creating a config file containing username and password that are then stored as env variables. These are then retrieved and used to submit my request.

One more question: could I omit username and password arguments from my url? And, instead of declaring them explicitly, add them in the header?

Thanks again.

import requests
#This is to log requests to database
from dotenv import dotenv_values
from dotenv import load_dotenv
import os

fileName = 'Some_config_file.txt'
path = '/home'

#Define config file
config = dotenv_values(os.path.join(path, fileName)) 

# Load the environment variables from the text file
load_dotenv(os.path.join(path, fileName))

# Read the credentials from the environment variables
username    = os.getenv("_USERNAME")
password    = os.getenv("_PASSWORD")
token       = os.getenv("_TOKEN")

# Create the header with the credentials
headers = {
    "Authorization": f"Bearer {token}"
}

# Construct the URL without the credentials <--- omit user and passwords, by including in header? 
url = "http://127.0.0.1:8000/endpoint/bearer?username={}&password={}".format(username, password)

#Submit Request
response = requests.get(url, headers=headers)

if response.status_code == 200:
    data = response.json()
    for key, value in data.items(): 
        print("{:15s} : {:10s}".format(str(key), str(value)))
else:
    print(f"Request failed with status code {response.status_code}")
vitalik commented 2 weeks ago

@domcyi24 yeah yoprobably better use post method for authentication

from ninja import Schema, Header

from ninja.security import HttpBearer

class TokenBearer(HttpBearer):
    def authenticate(self, request, token):
        if token == "supersecret":
            return token

@api = NinjaAPI(auth=TokenBearer())

class LoginSchema(Schema):
   username: str
   password: str

@api.post("/endpoint/bearer")
def user_login(request, body: LoginSchema):
        username = body.username
        password...
        ...login...

then your client code should look like this:


headers = {
    "Authorization": f"Bearer {token}"
}
payload = {
    "username": "your_username",
    "password": "your_password"
}

url = "http://127.0.0.1:8000/endpoint/bearer"

response = requests.post(url, json=payload, headers=headers)