Fastapi: [BUG] Missing form parameters when attempting to authorize with OpenAPI UI

Created on 9 Dec 2019  路  4Comments  路  Source: tiangolo/fastapi

Describe the bug

OpenAPI UI authorize button doesnt take all of the form parameters when trying to authenticate with a custom flow (i.e not the usual OAuth2PasswordBearer)

To Reproduce

from typing import Optional

from fastapi import Depends, FastAPI
from fastapi import HTTPException
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
from fastapi.params import Form
from fastapi.security.oauth2 import OAuth2
from fastapi.security.utils import get_authorization_scheme_param
from starlette.requests import Request
from starlette.status import HTTP_401_UNAUTHORIZED

fake_users_db = {
        "client_id": "x",
        "client_secret": "y",
        "disabled": False,
    }


app = FastAPI()


class Oauth2ClientCredentials(OAuth2):
    def __init__(
        self,
        tokenUrl: str,
        scheme_name: str = None,
        scopes: dict = None,
        auto_error: bool = True,
    ):
        if not scopes:
            scopes = {}
        flows = OAuthFlowsModel(
            clientCredentials={"tokenUrl": tokenUrl, "scopes": scopes}
        )
        super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)

    async def __call__(self, request: Request) -> Optional[str]:
        authorization: str = request.headers.get("Authorization")
        scheme, param = get_authorization_scheme_param(authorization)
        if not authorization or scheme.lower() != "bearer":
            if self.auto_error:
                raise HTTPException(
                    status_code=HTTP_401_UNAUTHORIZED,
                    detail="Not authenticated",
                    headers={"WWW-Authenticate": "Bearer"},
                )
            else:
                return None
        return param


oauth2_scheme = Oauth2ClientCredentials(
    tokenUrl="/token", scopes={"write": "write permission"}
)


async def get_current_user(token: str = Depends(oauth2_scheme)):
    return token


class ClientCredentials:
    """operates in the same way as fast apis 'OAuth2PasswordRequestForm'"""

    def __init__(
        self,
        grant_type: str = Form(None, regex="client_credentials"),
        client_id: str = Form(...),
        client_secret: str = Form(...),
        scope: str = Form(""),
    ):
        self.grant_type = grant_type
        self.client_id = client_id
        self.client_secret = client_secret
        self.scope = scope.split()


@app.post("/token")
async def login(form_data: ClientCredentials = Depends()):
    user_dict = form_data.client_id in fake_users_db.values()
    return {"made_up_bool_token": user_dict}


@app.get("/users/me")
async def read_users_me(current_user=Depends(get_current_user)):
    return current_user
  1. Create a file with the above
  2. run locally with uvicorn FILENAME:app --reload
  3. If you inspect the page after hitting the authorize key and filling in the respective fields with x, y and checking the write field, the params sent are grant_type=client_credentials&scope=write and are missing the client_id and client_secret parameters. Is this a bug or am I doing something silly?
bug

Most helpful comment

This ended up being a misunderstanding in how the swagger UI sends the required fields. It puts the client_id and secret in a header so I modified my endpoint to have the following params:

    grant_type: str = Form(...),
    scope: List = Form(None),
    authorization: str = Header(...)

and then did some manipulation from there. In the passwordbearer example you have the option of sending the params in the request body but when you change to client_credentials this is no longer the case. This allowed me to keep my customized Oauth2ClientCredentials class to maintain a clear UI for users. Hope this helps any others that run into this issue :)

All 4 comments

update: I have tried reverting to using oauth2_scheme = OAuth2PasswordBearer instead of oauth2_scheme = Oauth2ClientCredentials and if I ignore the username and password fields but fill out the client_id and client_secrets (and enable Client credentials location: request body) then I can get the correct form params to be picked up except for the grant_type which is password. This needs to be client_credentials in my case so it seems theres some issue between openapi and fastapi in creating the swagger schema when using a customized authorization class

looks like this might be related to my issue and might be more of an issue with implementation rather than a fastapi issue: https://github.com/swagger-api/swagger-ui/issues/4533

This ended up being a misunderstanding in how the swagger UI sends the required fields. It puts the client_id and secret in a header so I modified my endpoint to have the following params:

    grant_type: str = Form(...),
    scope: List = Form(None),
    authorization: str = Header(...)

and then did some manipulation from there. In the passwordbearer example you have the option of sending the params in the request body but when you change to client_credentials this is no longer the case. This allowed me to keep my customized Oauth2ClientCredentials class to maintain a clear UI for users. Hope this helps any others that run into this issue :)

Thanks for reporting back and closing the issue :+1:

Was this page helpful?
0 / 5 - 0 ratings