How and why to supply scalar scope to FastAPI oAuth2 scopes dict?

Question:

I am using FastAPI with Python 3.9. I haven’t been able to get the available oAuth2 dependencies to work with our particular Azure token authentication, and my initial attempt at using fastapi-azure-auth didn’t seem to match either.

I am therefore currently sub-classing fastapi.security.base.SecurityBase to try to create my own authentication dependency. I am using as a guide the approach in fastapi.security.oauth2.OAuth2 and fastapi.security.oauth2.OAuth2PasswordBearer.

These models rely on fastapi.openapi.models.OAuth2 and fastapi.openapi.models.OAuthFlow leading back to Pydantic’s BaseModel where presumably nothing much happens except initialising the fields that are provided.

The only information I can seem to find on using OAuth2 with FastAPI seem to be repetitious cut and pastes of the great little FastAPI security tutorial which only provides guidance for a simplistic dummy example.

At this stage I would just really like an answer to one question which is a puzzle for me: how are we supposed to supply scalar scopes in a dict?

  1. I have a "scope" that I believe is probably essential to be provided for the security scheme to work.
  2. The fastapi.security.oauth2.OAuth2 model needs to provide a fastapi.openapi.models.OAuth2 model for its model attribute.
  3. The fastapi.openapi.models.OAuth2 model needs to provide a fastapi.openapi.models.OAuthFlows model for its flows attribute.
  4. The OAuthFlows model contains one of the OAuthFlow<Type> models which sub-class fastapi.openapi.models.OAuthFlow.
  5. The OAuthFlow base class is where the "scopes" are stored: scopes: Dict[str, str] = {}

I can’t seem to find even one sentence on the behaviour and usage for OAuth2PasswordBearer right the way back to OAuthFlow, and even the code is completely bare of any in-line documentation for any of these classes.

But what seems to be clear from the FastAPI tutorial and OpenAPI documentation is that a "scope" is a string; and multiple scopes may sometimes be represented as a single string using space as a separator. I can’t avoid the conclusion (and the data I have available to supply as a scope confirms), that "scopes" are scalars: a single value.

https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/ says:

  • The OAuth2 specification defines "scopes" as a list of strings separated by spaces.
  • The content of each of these strings can have any format, but should not contain spaces.
  • Each "scope" is just a string (without spaces).

So my question is: how are we supposed to supply scalar values to the OAuthFlow.scopes dict?

My scope (scalar) looks like this kind of thing:

api://a12b34cd-5e67-89f0-a12b-c3de456f78ab/.default

Should this be supplied as the key, or the value, or both, and otherwise can the other key/value be left blank (""), None, or what should go in there (and why?)?

Also, since there is the fastapi.security.oauth2.SecurityScopes class that does store scalar scopes as space-separated strings, why are there two ways to store scopes and how do they interact (if at all)?

Asked By: NeilG

||

Answers:

After formulating this question I found a couple of lines in the FastAPI advanced user guide that I had missed. They refer back to the oAuth2 tutorial and add some example code that uses scopes:

There is one line in the example code there that defines two scopes:

scopes={"me": "Read information about the current user.", "items": "Read items."},

It appears that the actual scope is placed in the key of the scopes dictionary, and the value seems to be arbitrary descriptive text that presumably gets pushed out at an appropriate point somewhere (error message, exception, or at least just in the OpenAPI docs). This part of the FastAPI advanced user guide says:

The scopes parameter receives a dict with each scope as a key and the description as the value

So for my particular question it seems certain that I can supply my scope as:

scopes = {"api://a12b34cd-5e67-89f0-a12b-c3de456f78ab/.default": "access"}

As for the scalar scopes in SecurityScopes; when scopes are dealt with from the user, embedded in a token or checking incoming scopes against required scopes, no descriptive text is supplied, and scopes need to be checked iteratively, so a list of scopes is, as initially expected, appropriate and useful. I haven’t found any place where the SecurityScopes interact with the dict of scopes in OAuthFlow.

This turned out to be a much simpler question than I thought, but I think this is an example that demonstrates why in-line documentation is not an anti-pattern, as I hear some fanatics are espousing now.

To make the code stand on it’s own, and avoid tracing finely through the guide, the addition of a comment would help:

class OAuthFlow(BaseModel):
    refreshUrl: Optional[str] = None
    scopes: Dict[str, str] = {}  # [<scope>: <description>]

Or, now, even:

class OAuthFlow(BaseModel):
    refreshUrl: Optional[str] = None
    scopes: Dict[str, str] = {}  # https://stackoverflow.com/q/75091028

🙂

Answered By: NeilG
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.