fastapi icon indicating copy to clipboard operation
fastapi copied to clipboard

Improve `HTTPDigest` implementation

Open macheins opened this issue 4 years ago • 4 comments

Describe the bug

fastapi.security.HTTPDigest is actually not implementing HTTP Digest Access Authentication as specified by RFC 7616 or (the obsoleted) RFC 2617.

To Reproduce

Read the relevant RFCs or maybe try it out using curl --digest.

Expected behavior

Exactly as specified in RFC 7616.

macheins avatar May 25 '20 10:05 macheins

Yeah, it is used internally to be integrated with OpenAPI, even though the implementation is just the basic stub. But I would accept PRs improving it.

tiangolo avatar Jun 13 '20 16:06 tiangolo

I'll take this.

Kludex avatar Apr 11 '21 03:04 Kludex

I'm not taking this. I did something on https://github.com/tiangolo/fastapi/pull/3071 and closed, maybe it helps...

Kludex avatar Oct 19 '22 19:10 Kludex

Yeah, it is used internally to be integrated with OpenAPI, even though the implementation is just the basic stub. But I would accept PRs improving it.

Is this still open for improvement?

tfpgh avatar Jun 10 '23 18:06 tfpgh

@tiangolo Hey, I've looked into implementing this and I'm not actually sure it's possible. To authenticate the user or even slightly abstract the protocol away from the underlying application code, HTTPDigest would need access to a store of usernames and hashed passwords. What are your thoughts?

@Kludex I would also love to hear about what you discovered in your initial attempt at implementing this.

tfpgh avatar Jun 20 '23 17:06 tfpgh

I'll take this!

ordinary-jamie avatar Jul 07 '23 04:07 ordinary-jamie

Hi @tiangolo didn't realise how much work was involved with this challenge-response negotiation. I made a PR #9825 to attempt to implement this and used curl --digest as the basic benchmark to ensure it's consistent with the RFC.

Because of the way that FastAPI provides security dependencies + the hashing involved with the negotiation, I couldn't think of an easy way to allow authentication, besides exposing an .authenticate(user, pass) -> bool method on the returned DI object.

Let me know what you think!

ordinary-jamie avatar Jul 07 '23 10:07 ordinary-jamie

Hey @Kludex, would love to hear your thoughts on the best approach for this change. Keen to get his closed. Cheers

ordinary-jamie avatar Aug 29 '23 08:08 ordinary-jamie

Would love to see this implemented as well, trying to replicate a device for testing that uses digest, and it's been quite the headache.

That being said, I tried getting #9825 working, and it appears it doesn't work with pydantic when the version is ^2, but I was able to get it working with 1.10.12, is this worth updating to the latest pydantic version?

UpstreamData avatar Sep 04 '23 05:09 UpstreamData

Thanks @UpstreamData! I will look into it

ordinary-jamie avatar Sep 07 '23 00:09 ordinary-jamie

@UpstreamData I've fixed the PR to properly support both pydantic ^2 and 1.10 -- was an issue with the model config; decided to do away with arbitrary types all together. Tested locally and the quick curl test works for both versions.

ordinary-jamie avatar Sep 12 '23 06:09 ordinary-jamie

Just getting back around to this now, it's working great with the new changes, but I have a suggestion to improve something I find confusing. Entirely up to you if you'd like to implement this, just an idea I had that makes my use case easier.

I find the use of auto_error confusing, as setting it to False basically means the whole process needs to be re-implemented on the application end. Curious if it would make more sense to have auto_error control erroring for invalid credentials, and possibly have another flag that turns off the more implicit errors, such as digest auth being None? This would allow for logging on the application end when a user login fails, but would allow the security layer to hide some of the complexity that has to go on in the background in order to implement the digest auth.

Here's an example of what I was thinking would work, but instead causes errors on the server -

import logging
import secrets

import uvicorn
from fastapi import HTTPException, FastAPI, Depends
from fastapi.security import HTTPDigest
from starlette.requests import Request
from starlette.responses import Response

security = HTTPDigest(realm="my website", auto_error=False)


async def auth(
    request: Request,
    response: Response,
):
    # create digest auth dialog
    digest = await security(request)

    # authenticate credentials
    if not await digest.authenticate("root", "root"):
        logging.info("WEB - login failed")  # never logs anything with auto_error=True
        raise HTTPException(status_code=401)

    logging.info("WEB - login succeeded")
    return response


app = FastAPI(dependencies=[Depends(auth)])


@app.get("/")
async def index():
    return {"hello": "world"}


uvicorn.run(app)

UpstreamData avatar Oct 04 '23 19:10 UpstreamData