Open isedwards opened 3 years ago
Hello @isedwards,
According to this (https://stackoverflow.com/questions/62671257/import-hashed-password-from-flask-to-django) django uses the PBKDF2-sha256
hashing algorithm by default. We could also use the same algorithm in fastapi. I'd be happy to test this out if I can get the django application source to to ensure it's using the default hasher and also test data.
The migration discussed in the stack overflow post might also be something we can consider. If we can't figure out a way to reproduce the hashing algorithm on FastAPI's side of things, we can have the django application run parallel for a while to authenticate users and have them update their passwords. The new passwords will be stored with a known hash.
Or, instead of having to run the django parallel on a separate server. We can mount the django application on a particular path as described in this guide (https://fastapi.tiangolo.com/advanced/wsgi/). This feature hasn't been used extensively I believe however there has been next to no report on issues concerning this feature.
I'd suggest to go with approach one. I'd be happy to test it out and based on the outcome we can decide further.
I prefer the first option because it doesn't make Django a mandatory dependency. Could you have a go with a fresh install of Django 2.2.8?
We should have access to actual Django code base that we'll be interesting with in about 2 weeks.
There's another requirement I'd like to consider.... they have an existing Web API they've created using Django REST Framework that doesn't have authentication. Instead of adding authentication to their solution using Django, is there a sensible way for them to use our FastAPI solution to provide auth using the first option you mentioned and then expose their existing API?
@isedwards I will test this on a fresh install with Django 2.2.8. And yes absolutely once we are able to get the hashing right, we can recreate and produce the existing endpoints with FastAPI and also have them protected with JWT Bearer Tokens.
Hello @isedwards I was able to setup a django project with Django-2.2.8
and I created 2 users in the sqlite3
database using django-admin createsuperuser
and also using the User
model directly. Here is a sample of the password hash in the django database. pbkdf2_sha256$150000$h8igyXN7OAc9$27hvHOmtp9HS3zy8OdppcFJYFb28IC+IWozGgzwr7ps=
.
Good news is, I was able to verify the hash on FastApi side using passlib
. Apparently passlib directly supports django password hashing algorithms. (https://passlib.readthedocs.io/en/stable/lib/passlib.hash.django_std.html#django-1-4-hashes).
That's great news, thank you @Shaibujnr. Can you suggest options for securing a Django REST API by putting opencdms-server in front to manage authentication and requests (presumably either as a reverse proxy or using the WSGI mounting option that you mentioned)?
Hopefully we can use this to solve the issues here: https://github.com/opencdms/surface/issues/15
I've tried to illustrate this below:
@isedwards If I understand correctly.We have HTTP API endpoints currently in Django that are accessible by anyone and we would like to have a n authentication system for these endpoints right?
I don't think mounting would work in this case, since mounting would simply expose the Django application to the public on a specific path.
For this to work we would have to hide the the Django APIs from the public entirely. I'd suggest we run both servers, the FastAPI one would be exposed to the public and would implement the authentication and then the Django API will be served internally (only accessible by the FastAPI server). On successful authentication, the FastAPI would simply make an API request to the corresponding Django API.
Could you check and make sure? It sounds like the WSGI mounting approach "wraps" the other application - so presumably we could limit access to its end points based on whether the user is authenticated (but otherwise allow unmodified access)?
@isedwards FasAPI mounts WSGI applications using a WSGIMiddleware
this might be good news. Since it's a middle ware, it's most likely possible to have another middleware perform authentication before calling the WSGI app. I'll try this with the mock django application and give feedback by tomorrow.
Hello @isedwards
Good news, wrapping the WSGIMiddleware
with another custom middleware for authentication works well. Here is the snippet for the middleware.
class DjangoAuthenticationMiddleWare:
"""Middleware for authenticating a request before passing it on
to the mounted django application.
"""
def __init__(self, app: ASGIApp):
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send):
request = Request(scope, receive, send)
authorization_header = request.headers.get("Authorization")
if authorization_header is None:
raise HTTPException(401, "Unauthorized request")
scheme, token = get_authorization_scheme_param(authorization_header)
if scheme.lower() != "bearer":
raise HTTPException(401, "Invalid authorization header scheme")
# Token Validation code goes here
if token != "auth_token":
raise HTTPException(401, "Unauthorized request")
await self.app(scope, receive, send)
# Wrap django_application with WSGIMiddleware and DjangoAuthenticationMiddlewre
app.mount("/django", DjangoAuthenticationMiddleWare(WSGIMiddleware(django_application)))
This works well.
@isedwards https://github.com/opencdms/opencdms-server/pull/7
I have a branch make_mch_api_installable
that defines a setup.py
file for the mch_api flask applicaiton. This allowed me to install the mch-api
into opencdms_server
and then import and mount the flask applicaiton.
I couldn't follow this approach for the surface
django project. I could not successfully package the entire project to make it installable and importable. This is mainly because the manage.py
file required to run most of the django commands, is not in a package, it's located in the root folder.
As a work around, the opencdms_server
docker image, clones the surface
django project into its code source, installs all it's requirements and imports the wsgi application from there.
NOTE: I also had the set PYTHONPATH
environment variable to include the cloned surface
so that the opencdms_server
can import it successfully.
Thank you @Shaibujnr - this is looking excellent.
Could you submit a pull request to the mch-api
repository for your branch and we can get that merged.
Also, could you give an example of how we can log in and make authenticated API requests (do you have a method that you are using to do this when you are testing)?
Can access to our FastAPI API be managed using Django or directly with Django's
auth_user
table?