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?
- I have a "scope" that I believe is probably essential to be provided for the security scheme to work.
- The
fastapi.security.oauth2.OAuth2
model needs to provide a fastapi.openapi.models.OAuth2
model for its model
attribute.
- The
fastapi.openapi.models.OAuth2
model needs to provide a fastapi.openapi.models.OAuthFlows
model for its flows
attribute.
- The
OAuthFlows
model contains one of the OAuthFlow<Type>
models which sub-class fastapi.openapi.models.OAuthFlow
.
- 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)?
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
🙂
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?
- I have a "scope" that I believe is probably essential to be provided for the security scheme to work.
- The
fastapi.security.oauth2.OAuth2
model needs to provide afastapi.openapi.models.OAuth2
model for itsmodel
attribute. - The
fastapi.openapi.models.OAuth2
model needs to provide afastapi.openapi.models.OAuthFlows
model for itsflows
attribute. - The
OAuthFlows
model contains one of theOAuthFlow<Type>
models which sub-classfastapi.openapi.models.OAuthFlow
. - 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)?
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
🙂