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"]
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)
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
orfief_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.
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
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"]}
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.
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
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)
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.
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.