fief_client.integrations.fastapi

FastAPI integration.

  1"""FastAPI integration."""
  2
  3import uuid
  4from collections.abc import AsyncGenerator, Coroutine, Generator
  5from inspect import Parameter, Signature, isawaitable
  6from typing import (
  7    Callable,
  8    Optional,
  9    Protocol,
 10    TypeVar,
 11    Union,
 12    cast,
 13)
 14
 15from fastapi import Depends, HTTPException, Request, Response, status
 16from fastapi.security.base import SecurityBase
 17from fastapi.security.http import HTTPAuthorizationCredentials
 18from makefun import with_signature
 19
 20from fief_client import (
 21    Fief,
 22    FiefAccessTokenACRTooLow,
 23    FiefAccessTokenExpired,
 24    FiefAccessTokenInfo,
 25    FiefAccessTokenInvalid,
 26    FiefAccessTokenMissingPermission,
 27    FiefAccessTokenMissingScope,
 28    FiefACR,
 29    FiefAsync,
 30    FiefUserInfo,
 31)
 32
 33FiefClientClass = Union[Fief, FiefAsync]
 34
 35TokenType = Union[str, HTTPAuthorizationCredentials]
 36
 37RETURN_TYPE = TypeVar("RETURN_TYPE")
 38
 39DependencyCallable = Callable[
 40    ...,
 41    Union[
 42        RETURN_TYPE,
 43        Coroutine[None, None, RETURN_TYPE],
 44        AsyncGenerator[RETURN_TYPE, None],
 45        Generator[RETURN_TYPE, None, None],
 46    ],
 47]
 48
 49
 50class UserInfoCacheProtocol(Protocol):
 51    """
 52    Protocol that should follow a class to implement a cache mechanism for user information.
 53
 54    Read more: https://docs.fief.dev/integrate/python/fastapi/#caching-user-information
 55    """
 56
 57    async def get(self, user_id: uuid.UUID) -> Optional[FiefUserInfo]:
 58        """
 59        Retrieve user information from cache, if available.
 60
 61        :param user_id: The ID of the user to retrieve information for.
 62        """
 63        ...  # pragma: no cover
 64
 65    async def set(self, user_id: uuid.UUID, userinfo: FiefUserInfo) -> None:
 66        """
 67        Store user information in cache.
 68
 69        :param user_id: The ID of the user to cache information for.
 70        :param userinfo: The user information to cache.
 71        """
 72        ...  # pragma: no cover
 73
 74
 75class FiefAuth:
 76    """
 77    Helper class to integrate Fief authentication with FastAPI.
 78
 79    **Example:**
 80
 81    ```py
 82    from fastapi.security import OAuth2AuthorizationCodeBearer
 83    from fief_client import FiefAccessTokenInfo, FiefAsync
 84    from fief_client.integrations.fastapi import FiefAuth
 85
 86    fief = FiefAsync(
 87        "https://example.fief.dev",
 88        "YOUR_CLIENT_ID",
 89        "YOUR_CLIENT_SECRET",
 90    )
 91
 92    scheme = OAuth2AuthorizationCodeBearer(
 93        "https://example.fief.dev/authorize",
 94        "https://example.fief.dev/api/token",
 95        scopes={"openid": "openid", "offline_access": "offline_access"},
 96    )
 97
 98    auth = FiefAuth(fief, scheme)
 99    ```
100    """
101
102    def __init__(
103        self,
104        client: FiefClientClass,
105        scheme: SecurityBase,
106        *,
107        get_userinfo_cache: Optional[DependencyCallable[UserInfoCacheProtocol]] = None,
108    ) -> None:
109        """
110        :param client: Instance of a Fief client.
111        Can be either `fief_client.Fief` or `fief_client.FiefAsync`.
112        :param scheme: FastAPI security scheme.
113        It'll be used to retrieve the access token in the request.
114        :param get_userinfo_cache: Optional dependency returning an instance of a class
115        following the `UserInfoCacheProtocol`.
116        It'll be used to cache user information on your server.
117        Otherwise, the Fief API will always be reached when requesting user information.
118        """
119        self.client = client
120        self.scheme = scheme
121        self.get_userinfo_cache = get_userinfo_cache
122
123    def authenticated(
124        self,
125        optional: bool = False,
126        scope: Optional[list[str]] = None,
127        acr: Optional[FiefACR] = None,
128        permissions: Optional[list[str]] = None,
129    ):
130        """
131        Return a FastAPI dependency to check if a request is authenticated.
132
133        If the request is authenticated, the dependency will return a `fief_client.FiefAccessTokenInfo`.
134
135        :param optional: If `False` and the request is not authenticated,
136        an unauthorized response will be raised.
137        :param scope: Optional list of scopes required.
138        If the access token lacks one of the required scope, a forbidden response will be raised.
139        :param acr: Optional minimum ACR level required.
140        If the access token doesn't meet the minimum level, a forbidden response will be raised.
141        Read more: https://docs.fief.dev/going-further/acr/
142        :param permissions: Optional list of permissions required.
143        If the access token lacks one of the required permission, a forbidden response will be raised.
144
145        **Example**
146
147        ```py
148        @app.get("/authenticated")
149        async def get_authenticated(
150            access_token_info: FiefAccessTokenInfo = Depends(auth.authenticated()),
151        ):
152            return access_token_info
153        ```
154        """
155        signature = self._get_authenticated_call_signature(self.scheme)
156
157        @with_signature(signature)
158        async def _authenticated(
159            request: Request, response: Response, token: Optional[TokenType]
160        ) -> Optional[FiefAccessTokenInfo]:
161            if token is None:
162                if optional:
163                    return None
164                return await self.get_unauthorized_response(request, response)
165
166            if isinstance(token, HTTPAuthorizationCredentials):
167                token = token.credentials
168
169            try:
170                result = self.client.validate_access_token(
171                    token,
172                    required_scope=scope,
173                    required_acr=acr,
174                    required_permissions=permissions,
175                )
176                if isawaitable(result):
177                    info = await result
178                else:
179                    info = result
180            except (FiefAccessTokenInvalid, FiefAccessTokenExpired):
181                if optional:
182                    return None
183                return await self.get_unauthorized_response(request, response)
184            except (
185                FiefAccessTokenMissingScope,
186                FiefAccessTokenACRTooLow,
187                FiefAccessTokenMissingPermission,
188            ):
189                return await self.get_forbidden_response(request, response)
190
191            return info
192
193        return _authenticated
194
195    def current_user(
196        self,
197        optional: bool = False,
198        scope: Optional[list[str]] = None,
199        acr: Optional[FiefACR] = None,
200        permissions: Optional[list[str]] = None,
201        refresh: bool = False,
202    ):
203        """
204        Return a FastAPI dependency to check if a user is authenticated.
205
206        If the request is authenticated, the dependency will return a `fief_client.FiefUserInfo`.
207
208        If provided, the cache mechanism will be used to retrieve this information without calling the Fief API.
209
210        :param optional: If `False` and the request is not authenticated,
211        an unauthorized response will be raised.
212        :param scope: Optional list of scopes required.
213        If the access token lacks one of the required scope, a forbidden response will be raised.
214        :param acr: Optional minimum ACR level required.
215        If the access token doesn't meet the minimum level, a forbidden response will be raised.
216        Read more: https://docs.fief.dev/going-further/acr/
217        :param permissions: Optional list of permissions required.
218        If the access token lacks one of the required permission, a forbidden response will be raised.
219        :param refresh: If `True`, the user information will be refreshed from the Fief API.
220        Otherwise, the cache will be used.
221
222        **Example**
223
224        ```py
225        @app.get("/current-user", name="current_user")
226        async def get_current_user(
227            user: FiefUserInfo = Depends(auth.current_user()),
228        ):
229            return {"email": user["email"]}
230        ```
231        """
232        signature = self._get_current_user_call_signature(
233            self.authenticated(optional, scope, acr, permissions)
234        )
235
236        @with_signature(signature)
237        async def _current_user(
238            access_token_info: Optional[FiefAccessTokenInfo], *args, **kwargs
239        ) -> Optional[FiefUserInfo]:
240            userinfo_cache: Optional[UserInfoCacheProtocol] = kwargs.get(
241                "userinfo_cache"
242            )
243
244            if access_token_info is None and optional:
245                return None
246            assert access_token_info is not None
247
248            userinfo = None
249            if userinfo_cache is not None:
250                userinfo = await userinfo_cache.get(access_token_info["id"])
251
252            if userinfo is None or refresh:
253                result = self.client.userinfo(access_token_info["access_token"])
254                if isawaitable(result):
255                    userinfo = cast(FiefUserInfo, await result)
256                else:
257                    userinfo = cast(FiefUserInfo, result)
258
259                if userinfo_cache is not None:
260                    await userinfo_cache.set(access_token_info["id"], userinfo)
261
262            return userinfo
263
264        return _current_user
265
266    async def get_unauthorized_response(self, request: Request, response: Response):
267        """
268        Raise an `fastapi.HTTPException` with the status code 401.
269
270        This method is called when using the `authenticated` or `current_user` dependency
271        but the request is not authenticated.
272
273        You can override this method to customize the behavior in this case.
274        """
275        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
276
277    async def get_forbidden_response(self, request: Request, response: Response):
278        """
279        Raise an `fastapi.HTTPException` with the status code 403.
280
281        This method is called when using the `authenticated` or `current_user` dependency
282        but the access token doesn't match the list of scopes or permissions.
283
284        You can override this method to customize the behavior in this case.
285        """
286        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
287
288    def _get_authenticated_call_signature(self, scheme: SecurityBase) -> Signature:
289        """
290        Generate a dynamic signature for the authenticated dependency.
291        Here comes some blood magic 🧙‍♂️
292        Thank to "makefun", we are able to generate callable
293        with a dynamic security scheme dependency at runtime.
294        This way, it's detected by the OpenAPI generator.
295        """
296        parameters: list[Parameter] = [
297            Parameter(
298                name="request",
299                kind=Parameter.POSITIONAL_OR_KEYWORD,
300                annotation=Request,
301            ),
302            Parameter(
303                name="response",
304                kind=Parameter.POSITIONAL_OR_KEYWORD,
305                annotation=Response,
306            ),
307            Parameter(
308                name="token",
309                kind=Parameter.POSITIONAL_OR_KEYWORD,
310                default=Depends(cast(Callable, scheme)),
311            ),
312        ]
313
314        return Signature(parameters)
315
316    def _get_current_user_call_signature(self, authenticated: Callable) -> Signature:
317        """
318        Generate a dynamic signature for the current_user dependency.
319        Here comes some blood magic 🧙‍♂️
320        Thank to "makefun", we are able to generate callable
321        with a dynamic security scheme dependency at runtime.
322        This way, it's detected by the OpenAPI generator.
323        """
324        parameters: list[Parameter] = [
325            Parameter(
326                name="access_token_info",
327                kind=Parameter.POSITIONAL_OR_KEYWORD,
328                default=Depends(authenticated),
329                annotation=FiefAccessTokenInfo,
330            ),
331        ]
332
333        if self.get_userinfo_cache is not None:
334            parameters.append(
335                Parameter(
336                    name="userinfo_cache",
337                    kind=Parameter.POSITIONAL_OR_KEYWORD,
338                    default=Depends(self.get_userinfo_cache),
339                    annotation=UserInfoCacheProtocol,
340                ),
341            )
342
343        return Signature(parameters)
344
345
346__all__ = ["FiefAuth", "FiefClientClass", "UserInfoCacheProtocol"]
class FiefAuth:
 76class FiefAuth:
 77    """
 78    Helper class to integrate Fief authentication with FastAPI.
 79
 80    **Example:**
 81
 82    ```py
 83    from fastapi.security import OAuth2AuthorizationCodeBearer
 84    from fief_client import FiefAccessTokenInfo, FiefAsync
 85    from fief_client.integrations.fastapi import FiefAuth
 86
 87    fief = FiefAsync(
 88        "https://example.fief.dev",
 89        "YOUR_CLIENT_ID",
 90        "YOUR_CLIENT_SECRET",
 91    )
 92
 93    scheme = OAuth2AuthorizationCodeBearer(
 94        "https://example.fief.dev/authorize",
 95        "https://example.fief.dev/api/token",
 96        scopes={"openid": "openid", "offline_access": "offline_access"},
 97    )
 98
 99    auth = FiefAuth(fief, scheme)
100    ```
101    """
102
103    def __init__(
104        self,
105        client: FiefClientClass,
106        scheme: SecurityBase,
107        *,
108        get_userinfo_cache: Optional[DependencyCallable[UserInfoCacheProtocol]] = None,
109    ) -> None:
110        """
111        :param client: Instance of a Fief client.
112        Can be either `fief_client.Fief` or `fief_client.FiefAsync`.
113        :param scheme: FastAPI security scheme.
114        It'll be used to retrieve the access token in the request.
115        :param get_userinfo_cache: Optional dependency returning an instance of a class
116        following the `UserInfoCacheProtocol`.
117        It'll be used to cache user information on your server.
118        Otherwise, the Fief API will always be reached when requesting user information.
119        """
120        self.client = client
121        self.scheme = scheme
122        self.get_userinfo_cache = get_userinfo_cache
123
124    def authenticated(
125        self,
126        optional: bool = False,
127        scope: Optional[list[str]] = None,
128        acr: Optional[FiefACR] = None,
129        permissions: Optional[list[str]] = None,
130    ):
131        """
132        Return a FastAPI dependency to check if a request is authenticated.
133
134        If the request is authenticated, the dependency will return a `fief_client.FiefAccessTokenInfo`.
135
136        :param optional: If `False` and the request is not authenticated,
137        an unauthorized response will be raised.
138        :param scope: Optional list of scopes required.
139        If the access token lacks one of the required scope, a forbidden response will be raised.
140        :param acr: Optional minimum ACR level required.
141        If the access token doesn't meet the minimum level, a forbidden response will be raised.
142        Read more: https://docs.fief.dev/going-further/acr/
143        :param permissions: Optional list of permissions required.
144        If the access token lacks one of the required permission, a forbidden response will be raised.
145
146        **Example**
147
148        ```py
149        @app.get("/authenticated")
150        async def get_authenticated(
151            access_token_info: FiefAccessTokenInfo = Depends(auth.authenticated()),
152        ):
153            return access_token_info
154        ```
155        """
156        signature = self._get_authenticated_call_signature(self.scheme)
157
158        @with_signature(signature)
159        async def _authenticated(
160            request: Request, response: Response, token: Optional[TokenType]
161        ) -> Optional[FiefAccessTokenInfo]:
162            if token is None:
163                if optional:
164                    return None
165                return await self.get_unauthorized_response(request, response)
166
167            if isinstance(token, HTTPAuthorizationCredentials):
168                token = token.credentials
169
170            try:
171                result = self.client.validate_access_token(
172                    token,
173                    required_scope=scope,
174                    required_acr=acr,
175                    required_permissions=permissions,
176                )
177                if isawaitable(result):
178                    info = await result
179                else:
180                    info = result
181            except (FiefAccessTokenInvalid, FiefAccessTokenExpired):
182                if optional:
183                    return None
184                return await self.get_unauthorized_response(request, response)
185            except (
186                FiefAccessTokenMissingScope,
187                FiefAccessTokenACRTooLow,
188                FiefAccessTokenMissingPermission,
189            ):
190                return await self.get_forbidden_response(request, response)
191
192            return info
193
194        return _authenticated
195
196    def current_user(
197        self,
198        optional: bool = False,
199        scope: Optional[list[str]] = None,
200        acr: Optional[FiefACR] = None,
201        permissions: Optional[list[str]] = None,
202        refresh: bool = False,
203    ):
204        """
205        Return a FastAPI dependency to check if a user is authenticated.
206
207        If the request is authenticated, the dependency will return a `fief_client.FiefUserInfo`.
208
209        If provided, the cache mechanism will be used to retrieve this information without calling the Fief API.
210
211        :param optional: If `False` and the request is not authenticated,
212        an unauthorized response will be raised.
213        :param scope: Optional list of scopes required.
214        If the access token lacks one of the required scope, a forbidden response will be raised.
215        :param acr: Optional minimum ACR level required.
216        If the access token doesn't meet the minimum level, a forbidden response will be raised.
217        Read more: https://docs.fief.dev/going-further/acr/
218        :param permissions: Optional list of permissions required.
219        If the access token lacks one of the required permission, a forbidden response will be raised.
220        :param refresh: If `True`, the user information will be refreshed from the Fief API.
221        Otherwise, the cache will be used.
222
223        **Example**
224
225        ```py
226        @app.get("/current-user", name="current_user")
227        async def get_current_user(
228            user: FiefUserInfo = Depends(auth.current_user()),
229        ):
230            return {"email": user["email"]}
231        ```
232        """
233        signature = self._get_current_user_call_signature(
234            self.authenticated(optional, scope, acr, permissions)
235        )
236
237        @with_signature(signature)
238        async def _current_user(
239            access_token_info: Optional[FiefAccessTokenInfo], *args, **kwargs
240        ) -> Optional[FiefUserInfo]:
241            userinfo_cache: Optional[UserInfoCacheProtocol] = kwargs.get(
242                "userinfo_cache"
243            )
244
245            if access_token_info is None and optional:
246                return None
247            assert access_token_info is not None
248
249            userinfo = None
250            if userinfo_cache is not None:
251                userinfo = await userinfo_cache.get(access_token_info["id"])
252
253            if userinfo is None or refresh:
254                result = self.client.userinfo(access_token_info["access_token"])
255                if isawaitable(result):
256                    userinfo = cast(FiefUserInfo, await result)
257                else:
258                    userinfo = cast(FiefUserInfo, result)
259
260                if userinfo_cache is not None:
261                    await userinfo_cache.set(access_token_info["id"], userinfo)
262
263            return userinfo
264
265        return _current_user
266
267    async def get_unauthorized_response(self, request: Request, response: Response):
268        """
269        Raise an `fastapi.HTTPException` with the status code 401.
270
271        This method is called when using the `authenticated` or `current_user` dependency
272        but the request is not authenticated.
273
274        You can override this method to customize the behavior in this case.
275        """
276        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
277
278    async def get_forbidden_response(self, request: Request, response: Response):
279        """
280        Raise an `fastapi.HTTPException` with the status code 403.
281
282        This method is called when using the `authenticated` or `current_user` dependency
283        but the access token doesn't match the list of scopes or permissions.
284
285        You can override this method to customize the behavior in this case.
286        """
287        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
288
289    def _get_authenticated_call_signature(self, scheme: SecurityBase) -> Signature:
290        """
291        Generate a dynamic signature for the authenticated dependency.
292        Here comes some blood magic 🧙‍♂️
293        Thank to "makefun", we are able to generate callable
294        with a dynamic security scheme dependency at runtime.
295        This way, it's detected by the OpenAPI generator.
296        """
297        parameters: list[Parameter] = [
298            Parameter(
299                name="request",
300                kind=Parameter.POSITIONAL_OR_KEYWORD,
301                annotation=Request,
302            ),
303            Parameter(
304                name="response",
305                kind=Parameter.POSITIONAL_OR_KEYWORD,
306                annotation=Response,
307            ),
308            Parameter(
309                name="token",
310                kind=Parameter.POSITIONAL_OR_KEYWORD,
311                default=Depends(cast(Callable, scheme)),
312            ),
313        ]
314
315        return Signature(parameters)
316
317    def _get_current_user_call_signature(self, authenticated: Callable) -> Signature:
318        """
319        Generate a dynamic signature for the current_user dependency.
320        Here comes some blood magic 🧙‍♂️
321        Thank to "makefun", we are able to generate callable
322        with a dynamic security scheme dependency at runtime.
323        This way, it's detected by the OpenAPI generator.
324        """
325        parameters: list[Parameter] = [
326            Parameter(
327                name="access_token_info",
328                kind=Parameter.POSITIONAL_OR_KEYWORD,
329                default=Depends(authenticated),
330                annotation=FiefAccessTokenInfo,
331            ),
332        ]
333
334        if self.get_userinfo_cache is not None:
335            parameters.append(
336                Parameter(
337                    name="userinfo_cache",
338                    kind=Parameter.POSITIONAL_OR_KEYWORD,
339                    default=Depends(self.get_userinfo_cache),
340                    annotation=UserInfoCacheProtocol,
341                ),
342            )
343
344        return Signature(parameters)

Helper class to integrate Fief authentication with FastAPI.

Example:

from fastapi.security import OAuth2AuthorizationCodeBearer
from fief_client import FiefAccessTokenInfo, FiefAsync
from fief_client.integrations.fastapi import FiefAuth

fief = FiefAsync(
    "https://example.fief.dev",
    "YOUR_CLIENT_ID",
    "YOUR_CLIENT_SECRET",
)

scheme = OAuth2AuthorizationCodeBearer(
    "https://example.fief.dev/authorize",
    "https://example.fief.dev/api/token",
    scopes={"openid": "openid", "offline_access": "offline_access"},
)

auth = FiefAuth(fief, scheme)
FiefAuth( client: Union[fief_client.Fief, fief_client.FiefAsync], scheme: fastapi.security.base.SecurityBase, *, get_userinfo_cache: Optional[Callable[..., Union[UserInfoCacheProtocol, Coroutine[None, None, UserInfoCacheProtocol], AsyncGenerator[UserInfoCacheProtocol, None], Generator[UserInfoCacheProtocol, None, None]]]] = None)
103    def __init__(
104        self,
105        client: FiefClientClass,
106        scheme: SecurityBase,
107        *,
108        get_userinfo_cache: Optional[DependencyCallable[UserInfoCacheProtocol]] = None,
109    ) -> None:
110        """
111        :param client: Instance of a Fief client.
112        Can be either `fief_client.Fief` or `fief_client.FiefAsync`.
113        :param scheme: FastAPI security scheme.
114        It'll be used to retrieve the access token in the request.
115        :param get_userinfo_cache: Optional dependency returning an instance of a class
116        following the `UserInfoCacheProtocol`.
117        It'll be used to cache user information on your server.
118        Otherwise, the Fief API will always be reached when requesting user information.
119        """
120        self.client = client
121        self.scheme = scheme
122        self.get_userinfo_cache = get_userinfo_cache
Parameters
  • client: Instance of a Fief client. Can be either fief_client.Fief or fief_client.FiefAsync.
  • scheme: FastAPI security scheme. It'll be used to retrieve the access token in the request.
  • get_userinfo_cache: Optional dependency returning an instance of a class following the UserInfoCacheProtocol. It'll be used to cache user information on your server. Otherwise, the Fief API will always be reached when requesting user information.
client
scheme
get_userinfo_cache
def authenticated( self, optional: bool = False, scope: Optional[list[str]] = None, acr: Optional[fief_client.FiefACR] = None, permissions: Optional[list[str]] = None):
124    def authenticated(
125        self,
126        optional: bool = False,
127        scope: Optional[list[str]] = None,
128        acr: Optional[FiefACR] = None,
129        permissions: Optional[list[str]] = None,
130    ):
131        """
132        Return a FastAPI dependency to check if a request is authenticated.
133
134        If the request is authenticated, the dependency will return a `fief_client.FiefAccessTokenInfo`.
135
136        :param optional: If `False` and the request is not authenticated,
137        an unauthorized response will be raised.
138        :param scope: Optional list of scopes required.
139        If the access token lacks one of the required scope, a forbidden response will be raised.
140        :param acr: Optional minimum ACR level required.
141        If the access token doesn't meet the minimum level, a forbidden response will be raised.
142        Read more: https://docs.fief.dev/going-further/acr/
143        :param permissions: Optional list of permissions required.
144        If the access token lacks one of the required permission, a forbidden response will be raised.
145
146        **Example**
147
148        ```py
149        @app.get("/authenticated")
150        async def get_authenticated(
151            access_token_info: FiefAccessTokenInfo = Depends(auth.authenticated()),
152        ):
153            return access_token_info
154        ```
155        """
156        signature = self._get_authenticated_call_signature(self.scheme)
157
158        @with_signature(signature)
159        async def _authenticated(
160            request: Request, response: Response, token: Optional[TokenType]
161        ) -> Optional[FiefAccessTokenInfo]:
162            if token is None:
163                if optional:
164                    return None
165                return await self.get_unauthorized_response(request, response)
166
167            if isinstance(token, HTTPAuthorizationCredentials):
168                token = token.credentials
169
170            try:
171                result = self.client.validate_access_token(
172                    token,
173                    required_scope=scope,
174                    required_acr=acr,
175                    required_permissions=permissions,
176                )
177                if isawaitable(result):
178                    info = await result
179                else:
180                    info = result
181            except (FiefAccessTokenInvalid, FiefAccessTokenExpired):
182                if optional:
183                    return None
184                return await self.get_unauthorized_response(request, response)
185            except (
186                FiefAccessTokenMissingScope,
187                FiefAccessTokenACRTooLow,
188                FiefAccessTokenMissingPermission,
189            ):
190                return await self.get_forbidden_response(request, response)
191
192            return info
193
194        return _authenticated

Return a FastAPI dependency to check if a request is authenticated.

If the request is authenticated, the dependency will return a fief_client.FiefAccessTokenInfo.

Parameters
  • optional: If False and the request is not authenticated, an unauthorized response will be raised.
  • scope: Optional list of scopes required. If the access token lacks one of the required scope, a forbidden response will be raised.
  • acr: Optional minimum ACR level required. If the access token doesn't meet the minimum level, a forbidden response will be raised. Read more: https://docs.fief.dev/going-further/acr/
  • permissions: Optional list of permissions required. If the access token lacks one of the required permission, a forbidden response will be raised.

Example

@app.get("/authenticated")
async def get_authenticated(
    access_token_info: FiefAccessTokenInfo = Depends(auth.authenticated()),
):
    return access_token_info
def current_user( self, optional: bool = False, scope: Optional[list[str]] = None, acr: Optional[fief_client.FiefACR] = None, permissions: Optional[list[str]] = None, refresh: bool = False):
196    def current_user(
197        self,
198        optional: bool = False,
199        scope: Optional[list[str]] = None,
200        acr: Optional[FiefACR] = None,
201        permissions: Optional[list[str]] = None,
202        refresh: bool = False,
203    ):
204        """
205        Return a FastAPI dependency to check if a user is authenticated.
206
207        If the request is authenticated, the dependency will return a `fief_client.FiefUserInfo`.
208
209        If provided, the cache mechanism will be used to retrieve this information without calling the Fief API.
210
211        :param optional: If `False` and the request is not authenticated,
212        an unauthorized response will be raised.
213        :param scope: Optional list of scopes required.
214        If the access token lacks one of the required scope, a forbidden response will be raised.
215        :param acr: Optional minimum ACR level required.
216        If the access token doesn't meet the minimum level, a forbidden response will be raised.
217        Read more: https://docs.fief.dev/going-further/acr/
218        :param permissions: Optional list of permissions required.
219        If the access token lacks one of the required permission, a forbidden response will be raised.
220        :param refresh: If `True`, the user information will be refreshed from the Fief API.
221        Otherwise, the cache will be used.
222
223        **Example**
224
225        ```py
226        @app.get("/current-user", name="current_user")
227        async def get_current_user(
228            user: FiefUserInfo = Depends(auth.current_user()),
229        ):
230            return {"email": user["email"]}
231        ```
232        """
233        signature = self._get_current_user_call_signature(
234            self.authenticated(optional, scope, acr, permissions)
235        )
236
237        @with_signature(signature)
238        async def _current_user(
239            access_token_info: Optional[FiefAccessTokenInfo], *args, **kwargs
240        ) -> Optional[FiefUserInfo]:
241            userinfo_cache: Optional[UserInfoCacheProtocol] = kwargs.get(
242                "userinfo_cache"
243            )
244
245            if access_token_info is None and optional:
246                return None
247            assert access_token_info is not None
248
249            userinfo = None
250            if userinfo_cache is not None:
251                userinfo = await userinfo_cache.get(access_token_info["id"])
252
253            if userinfo is None or refresh:
254                result = self.client.userinfo(access_token_info["access_token"])
255                if isawaitable(result):
256                    userinfo = cast(FiefUserInfo, await result)
257                else:
258                    userinfo = cast(FiefUserInfo, result)
259
260                if userinfo_cache is not None:
261                    await userinfo_cache.set(access_token_info["id"], userinfo)
262
263            return userinfo
264
265        return _current_user

Return a FastAPI dependency to check if a user is authenticated.

If the request is authenticated, the dependency will return a fief_client.FiefUserInfo.

If provided, the cache mechanism will be used to retrieve this information without calling the Fief API.

Parameters
  • optional: If False and the request is not authenticated, an unauthorized response will be raised.
  • scope: Optional list of scopes required. If the access token lacks one of the required scope, a forbidden response will be raised.
  • acr: Optional minimum ACR level required. If the access token doesn't meet the minimum level, a forbidden response will be raised. Read more: https://docs.fief.dev/going-further/acr/
  • permissions: Optional list of permissions required. If the access token lacks one of the required permission, a forbidden response will be raised.
  • refresh: If True, the user information will be refreshed from the Fief API. Otherwise, the cache will be used.

Example

@app.get("/current-user", name="current_user")
async def get_current_user(
    user: FiefUserInfo = Depends(auth.current_user()),
):
    return {"email": user["email"]}
async def get_unauthorized_response( self, request: starlette.requests.Request, response: starlette.responses.Response):
267    async def get_unauthorized_response(self, request: Request, response: Response):
268        """
269        Raise an `fastapi.HTTPException` with the status code 401.
270
271        This method is called when using the `authenticated` or `current_user` dependency
272        but the request is not authenticated.
273
274        You can override this method to customize the behavior in this case.
275        """
276        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)

Raise an fastapi.HTTPException with the status code 401.

This method is called when using the authenticated or current_user dependency but the request is not authenticated.

You can override this method to customize the behavior in this case.

async def get_forbidden_response( self, request: starlette.requests.Request, response: starlette.responses.Response):
278    async def get_forbidden_response(self, request: Request, response: Response):
279        """
280        Raise an `fastapi.HTTPException` with the status code 403.
281
282        This method is called when using the `authenticated` or `current_user` dependency
283        but the access token doesn't match the list of scopes or permissions.
284
285        You can override this method to customize the behavior in this case.
286        """
287        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)

Raise an fastapi.HTTPException with the status code 403.

This method is called when using the authenticated or current_user dependency but the access token doesn't match the list of scopes or permissions.

You can override this method to customize the behavior in this case.

FiefClientClass = typing.Union[fief_client.Fief, fief_client.FiefAsync]
class UserInfoCacheProtocol(typing.Protocol):
51class UserInfoCacheProtocol(Protocol):
52    """
53    Protocol that should follow a class to implement a cache mechanism for user information.
54
55    Read more: https://docs.fief.dev/integrate/python/fastapi/#caching-user-information
56    """
57
58    async def get(self, user_id: uuid.UUID) -> Optional[FiefUserInfo]:
59        """
60        Retrieve user information from cache, if available.
61
62        :param user_id: The ID of the user to retrieve information for.
63        """
64        ...  # pragma: no cover
65
66    async def set(self, user_id: uuid.UUID, userinfo: FiefUserInfo) -> None:
67        """
68        Store user information in cache.
69
70        :param user_id: The ID of the user to cache information for.
71        :param userinfo: The user information to cache.
72        """
73        ...  # pragma: no cover

Protocol that should follow a class to implement a cache mechanism for user information.

Read more: https://docs.fief.dev/integrate/python/fastapi/#caching-user-information

UserInfoCacheProtocol(*args, **kwargs)
1431def _no_init_or_replace_init(self, *args, **kwargs):
1432    cls = type(self)
1433
1434    if cls._is_protocol:
1435        raise TypeError('Protocols cannot be instantiated')
1436
1437    # Already using a custom `__init__`. No need to calculate correct
1438    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1439    if cls.__init__ is not _no_init_or_replace_init:
1440        return
1441
1442    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1443    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1444    # searches for a proper new `__init__` in the MRO. The new `__init__`
1445    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1446    # instantiation of the protocol subclass will thus use the new
1447    # `__init__` and no longer call `_no_init_or_replace_init`.
1448    for base in cls.__mro__:
1449        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1450        if init is not _no_init_or_replace_init:
1451            cls.__init__ = init
1452            break
1453    else:
1454        # should not happen
1455        cls.__init__ = object.__init__
1456
1457    cls.__init__(self, *args, **kwargs)
async def get(self, user_id: uuid.UUID) -> Optional[fief_client.FiefUserInfo]:
58    async def get(self, user_id: uuid.UUID) -> Optional[FiefUserInfo]:
59        """
60        Retrieve user information from cache, if available.
61
62        :param user_id: The ID of the user to retrieve information for.
63        """
64        ...  # pragma: no cover

Retrieve user information from cache, if available.

Parameters
  • user_id: The ID of the user to retrieve information for.
async def set( self, user_id: uuid.UUID, userinfo: fief_client.FiefUserInfo) -> None:
66    async def set(self, user_id: uuid.UUID, userinfo: FiefUserInfo) -> None:
67        """
68        Store user information in cache.
69
70        :param user_id: The ID of the user to cache information for.
71        :param userinfo: The user information to cache.
72        """
73        ...  # pragma: no cover

Store user information in cache.

Parameters
  • user_id: The ID of the user to cache information for.
  • userinfo: The user information to cache.