fief_client.integrations.flask
Flask integration.
1"""Flask integration.""" 2 3import uuid 4from functools import wraps 5from typing import Callable, Optional 6 7from flask import g, request 8 9from fief_client import ( 10 Fief, 11 FiefAccessTokenACRTooLow, 12 FiefAccessTokenExpired, 13 FiefAccessTokenInfo, 14 FiefAccessTokenInvalid, 15 FiefAccessTokenMissingScope, 16 FiefACR, 17 FiefUserInfo, 18) 19from fief_client.client import FiefAccessTokenMissingPermission 20 21 22class FiefAuthError(Exception): 23 """ 24 Base error for FiefAuth integration. 25 """ 26 27 28class FiefAuthUnauthorized(FiefAuthError): 29 """ 30 Request unauthorized error. 31 32 This error is raised when using the `authenticated` or `current_user` decorator 33 but the request is not authenticated. 34 35 You should implement an `errorhandler` to define the behavior of your server when 36 this happens. 37 38 **Example:** 39 40 ```py 41 @app.errorhandler(FiefAuthUnauthorized) 42 def fief_unauthorized_error(e): 43 return "", 401 44 ``` 45 """ 46 47 48class FiefAuthForbidden(FiefAuthError): 49 """ 50 Request forbidden error. 51 52 This error is raised when using the `authenticated` or `current_user` decorator 53 but the access token doesn't match the list of scopes, permissions or minimum ACR level. 54 55 You should implement an `errorhandler` to define the behavior of your server when 56 this happens. 57 58 **Example:** 59 60 ```py 61 @app.errorhandler(FiefAuthForbidden) 62 def fief_forbidden_error(e): 63 return "", 403 64 ``` 65 """ 66 67 68TokenGetter = Callable[[], Optional[str]] 69"""Type of a function that can be used to retrieve a token.""" 70 71UserInfoCacheGetter = Callable[[uuid.UUID], Optional[FiefUserInfo]] 72""" 73Type of a function that can be used to retrieve user information from a cache. 74 75Read more: https://docs.fief.dev/integrate/python/flask/#web-application-example 76""" 77 78UserInfoCacheSetter = Callable[[uuid.UUID, FiefUserInfo], None] 79""" 80Type of a function that can be used to store user information in a cache. 81 82Read more: https://docs.fief.dev/integrate/python/flask/#web-application-example 83""" 84 85 86def get_authorization_scheme_token(*, scheme: str = "bearer") -> TokenGetter: 87 """ 88 Return a `TokenGetter` function to retrieve a token from the `Authorization` header of an HTTP request. 89 90 :param scheme: Scheme of the token. Defaults to `bearer`. 91 """ 92 93 def _get_authorization_scheme_token(): 94 authorization = request.headers.get("Authorization") 95 if authorization is None: 96 return None 97 parts = authorization.split() 98 if len(parts) != 2 or parts[0].lower() != scheme.lower(): 99 return None 100 return parts[1] 101 102 return _get_authorization_scheme_token 103 104 105def get_cookie(cookie_name: str) -> TokenGetter: 106 """ 107 Return a `TokenGetter` function to retrieve a token from a `Cookie` of an HTTP request. 108 109 :param cookie_name: Name of the cookie. 110 """ 111 112 def _get_cookie(): 113 return request.cookies.get(cookie_name) 114 115 return _get_cookie 116 117 118class FiefAuth: 119 """ 120 Helper class to integrate Fief authentication with Flask. 121 122 **Example:** 123 124 ```py 125 from fief_client import Fief 126 from fief_client.integrations.flask import ( 127 FiefAuth, 128 get_authorization_scheme_token, 129 ) 130 from flask import Flask, g 131 132 fief = Fief( 133 "https://example.fief.dev", 134 "YOUR_CLIENT_ID", 135 "YOUR_CLIENT_SECRET", 136 ) 137 138 auth = FiefAuth(fief, get_authorization_scheme_token()) 139 140 app = Flask(__name__) 141 ``` 142 """ 143 144 def __init__( 145 self, 146 client: Fief, 147 token_getter: TokenGetter, 148 *, 149 get_userinfo_cache: Optional[UserInfoCacheGetter] = None, 150 set_userinfo_cache: Optional[UserInfoCacheSetter] = None, 151 ) -> None: 152 """ 153 :param client: Instance of a `fief_client.Fief` client. 154 :param token_getter: Function to retrieve a token. 155 It should follow the `TokenGetter` type. 156 :param get_userinfo_cache: Optional function to retrieve user information from a cache. 157 Otherwise, the Fief API will always be reached when requesting user information. 158 It should follow the `UserInfoCacheGetter` type. 159 :param set_userinfo_cache: Optional function to store user information in a cache. 160 It should follow the `UserInfoCacheSetter` type. 161 """ 162 self.client = client 163 self.token_getter = token_getter 164 self.get_userinfo_cache = get_userinfo_cache 165 self.set_userinfo_cache = set_userinfo_cache 166 167 def authenticated( 168 self, 169 *, 170 optional: bool = False, 171 scope: Optional[list[str]] = None, 172 acr: Optional[FiefACR] = None, 173 permissions: Optional[list[str]] = None, 174 ): 175 """ 176 Decorator to check if a request is authenticated. 177 178 If the request is authenticated, the `g` object will have an `access_token_info` property, 179 of type `fief_client.FiefAccessTokenInfo`. 180 181 :param optional: If `False` and the request is not authenticated, 182 a `FiefAuthUnauthorized` error will be raised. 183 :param scope: Optional list of scopes required. 184 If the access token lacks one of the required scope, a `FiefAuthForbidden` error will be raised. 185 :param acr: Optional minimum ACR level required. 186 If the access token doesn't meet the minimum level, a `FiefAuthForbidden` error will be raised. 187 Read more: https://docs.fief.dev/going-further/acr/ 188 :param permissions: Optional list of permissions required. 189 If the access token lacks one of the required permission, a `FiefAuthForbidden` error will be raised. 190 191 **Example** 192 193 ```py 194 @app.get("/authenticated") 195 @auth.authenticated() 196 def get_authenticated(): 197 return g.access_token_info 198 ``` 199 """ 200 201 def _authenticated(f): 202 @wraps(f) 203 def decorated_function(*args, **kwargs): 204 token = self.token_getter() 205 if token is None: 206 if optional: 207 g.access_token_info = None 208 return f(*args, **kwargs) 209 raise FiefAuthUnauthorized() 210 211 try: 212 info = self.client.validate_access_token( 213 token, 214 required_scope=scope, 215 required_acr=acr, 216 required_permissions=permissions, 217 ) 218 except (FiefAccessTokenInvalid, FiefAccessTokenExpired) as e: 219 if optional: 220 g.access_token_info = None 221 return f(*args, **kwargs) 222 raise FiefAuthUnauthorized() from e 223 except ( 224 FiefAccessTokenMissingScope, 225 FiefAccessTokenACRTooLow, 226 FiefAccessTokenMissingPermission, 227 ) as e: 228 raise FiefAuthForbidden() from e 229 230 g.access_token_info = info 231 232 return f(*args, **kwargs) 233 234 return decorated_function 235 236 return _authenticated 237 238 def current_user( 239 self, 240 *, 241 optional: bool = False, 242 scope: Optional[list[str]] = None, 243 acr: Optional[FiefACR] = None, 244 permissions: Optional[list[str]] = None, 245 refresh: bool = False, 246 ): 247 """ 248 Decorator to check if a user is authenticated. 249 250 If the request is authenticated, the `g` object will have a `user` property, 251 of type `fief_client.FiefUserInfo`. 252 253 :param optional: If `False` and the request is not authenticated, 254 a `FiefAuthUnauthorized` error will be raised. 255 :param scope: Optional list of scopes required. 256 If the access token lacks one of the required scope, a `FiefAuthForbidden` error will be raised. 257 :param acr: Optional minimum ACR level required. 258 If the access token doesn't meet the minimum level, a `FiefAuthForbidden` error will be raised. 259 Read more: https://docs.fief.dev/going-further/acr/ 260 :param permissions: Optional list of permissions required. 261 If the access token lacks one of the required permission, a `FiefAuthForbidden` error will be raised. 262 :param refresh: If `True`, the user information will be refreshed from the Fief API. 263 Otherwise, the cache will be used. 264 265 **Example** 266 267 ```py 268 @app.get("/current-user") 269 @auth.current_user() 270 def get_current_user(): 271 user = g.user 272 return f"<h1>You are authenticated. Your user email is {user['email']}</h1>" 273 ``` 274 """ 275 276 def _current_user(f): 277 @wraps(f) 278 @self.authenticated( 279 optional=optional, scope=scope, acr=acr, permissions=permissions 280 ) 281 def decorated_function(*args, **kwargs): 282 access_token_info: Optional[FiefAccessTokenInfo] = g.access_token_info 283 284 if access_token_info is None and optional: 285 g.user = None 286 return f(*args, **kwargs) 287 288 assert access_token_info is not None 289 290 userinfo = None 291 if self.get_userinfo_cache is not None: 292 userinfo = self.get_userinfo_cache(access_token_info["id"]) 293 294 if userinfo is None or refresh: 295 userinfo = self.client.userinfo(access_token_info["access_token"]) 296 297 if self.set_userinfo_cache is not None: 298 self.set_userinfo_cache(access_token_info["id"], userinfo) 299 300 g.user = userinfo 301 302 return f(*args, **kwargs) 303 304 return decorated_function 305 306 return _current_user 307 308 309__all__ = [ 310 "FiefAuth", 311 "FiefAuthError", 312 "FiefAuthUnauthorized", 313 "FiefAuthForbidden", 314 "TokenGetter", 315 "UserInfoCacheGetter", 316 "UserInfoCacheSetter", 317 "get_authorization_scheme_token", 318 "get_cookie", 319]
119class FiefAuth: 120 """ 121 Helper class to integrate Fief authentication with Flask. 122 123 **Example:** 124 125 ```py 126 from fief_client import Fief 127 from fief_client.integrations.flask import ( 128 FiefAuth, 129 get_authorization_scheme_token, 130 ) 131 from flask import Flask, g 132 133 fief = Fief( 134 "https://example.fief.dev", 135 "YOUR_CLIENT_ID", 136 "YOUR_CLIENT_SECRET", 137 ) 138 139 auth = FiefAuth(fief, get_authorization_scheme_token()) 140 141 app = Flask(__name__) 142 ``` 143 """ 144 145 def __init__( 146 self, 147 client: Fief, 148 token_getter: TokenGetter, 149 *, 150 get_userinfo_cache: Optional[UserInfoCacheGetter] = None, 151 set_userinfo_cache: Optional[UserInfoCacheSetter] = None, 152 ) -> None: 153 """ 154 :param client: Instance of a `fief_client.Fief` client. 155 :param token_getter: Function to retrieve a token. 156 It should follow the `TokenGetter` type. 157 :param get_userinfo_cache: Optional function to retrieve user information from a cache. 158 Otherwise, the Fief API will always be reached when requesting user information. 159 It should follow the `UserInfoCacheGetter` type. 160 :param set_userinfo_cache: Optional function to store user information in a cache. 161 It should follow the `UserInfoCacheSetter` type. 162 """ 163 self.client = client 164 self.token_getter = token_getter 165 self.get_userinfo_cache = get_userinfo_cache 166 self.set_userinfo_cache = set_userinfo_cache 167 168 def authenticated( 169 self, 170 *, 171 optional: bool = False, 172 scope: Optional[list[str]] = None, 173 acr: Optional[FiefACR] = None, 174 permissions: Optional[list[str]] = None, 175 ): 176 """ 177 Decorator to check if a request is authenticated. 178 179 If the request is authenticated, the `g` object will have an `access_token_info` property, 180 of type `fief_client.FiefAccessTokenInfo`. 181 182 :param optional: If `False` and the request is not authenticated, 183 a `FiefAuthUnauthorized` error will be raised. 184 :param scope: Optional list of scopes required. 185 If the access token lacks one of the required scope, a `FiefAuthForbidden` error will be raised. 186 :param acr: Optional minimum ACR level required. 187 If the access token doesn't meet the minimum level, a `FiefAuthForbidden` error will be raised. 188 Read more: https://docs.fief.dev/going-further/acr/ 189 :param permissions: Optional list of permissions required. 190 If the access token lacks one of the required permission, a `FiefAuthForbidden` error will be raised. 191 192 **Example** 193 194 ```py 195 @app.get("/authenticated") 196 @auth.authenticated() 197 def get_authenticated(): 198 return g.access_token_info 199 ``` 200 """ 201 202 def _authenticated(f): 203 @wraps(f) 204 def decorated_function(*args, **kwargs): 205 token = self.token_getter() 206 if token is None: 207 if optional: 208 g.access_token_info = None 209 return f(*args, **kwargs) 210 raise FiefAuthUnauthorized() 211 212 try: 213 info = self.client.validate_access_token( 214 token, 215 required_scope=scope, 216 required_acr=acr, 217 required_permissions=permissions, 218 ) 219 except (FiefAccessTokenInvalid, FiefAccessTokenExpired) as e: 220 if optional: 221 g.access_token_info = None 222 return f(*args, **kwargs) 223 raise FiefAuthUnauthorized() from e 224 except ( 225 FiefAccessTokenMissingScope, 226 FiefAccessTokenACRTooLow, 227 FiefAccessTokenMissingPermission, 228 ) as e: 229 raise FiefAuthForbidden() from e 230 231 g.access_token_info = info 232 233 return f(*args, **kwargs) 234 235 return decorated_function 236 237 return _authenticated 238 239 def current_user( 240 self, 241 *, 242 optional: bool = False, 243 scope: Optional[list[str]] = None, 244 acr: Optional[FiefACR] = None, 245 permissions: Optional[list[str]] = None, 246 refresh: bool = False, 247 ): 248 """ 249 Decorator to check if a user is authenticated. 250 251 If the request is authenticated, the `g` object will have a `user` property, 252 of type `fief_client.FiefUserInfo`. 253 254 :param optional: If `False` and the request is not authenticated, 255 a `FiefAuthUnauthorized` error will be raised. 256 :param scope: Optional list of scopes required. 257 If the access token lacks one of the required scope, a `FiefAuthForbidden` error will be raised. 258 :param acr: Optional minimum ACR level required. 259 If the access token doesn't meet the minimum level, a `FiefAuthForbidden` error will be raised. 260 Read more: https://docs.fief.dev/going-further/acr/ 261 :param permissions: Optional list of permissions required. 262 If the access token lacks one of the required permission, a `FiefAuthForbidden` error will be raised. 263 :param refresh: If `True`, the user information will be refreshed from the Fief API. 264 Otherwise, the cache will be used. 265 266 **Example** 267 268 ```py 269 @app.get("/current-user") 270 @auth.current_user() 271 def get_current_user(): 272 user = g.user 273 return f"<h1>You are authenticated. Your user email is {user['email']}</h1>" 274 ``` 275 """ 276 277 def _current_user(f): 278 @wraps(f) 279 @self.authenticated( 280 optional=optional, scope=scope, acr=acr, permissions=permissions 281 ) 282 def decorated_function(*args, **kwargs): 283 access_token_info: Optional[FiefAccessTokenInfo] = g.access_token_info 284 285 if access_token_info is None and optional: 286 g.user = None 287 return f(*args, **kwargs) 288 289 assert access_token_info is not None 290 291 userinfo = None 292 if self.get_userinfo_cache is not None: 293 userinfo = self.get_userinfo_cache(access_token_info["id"]) 294 295 if userinfo is None or refresh: 296 userinfo = self.client.userinfo(access_token_info["access_token"]) 297 298 if self.set_userinfo_cache is not None: 299 self.set_userinfo_cache(access_token_info["id"], userinfo) 300 301 g.user = userinfo 302 303 return f(*args, **kwargs) 304 305 return decorated_function 306 307 return _current_user
Helper class to integrate Fief authentication with Flask.
Example:
from fief_client import Fief
from fief_client.integrations.flask import (
FiefAuth,
get_authorization_scheme_token,
)
from flask import Flask, g
fief = Fief(
"https://example.fief.dev",
"YOUR_CLIENT_ID",
"YOUR_CLIENT_SECRET",
)
auth = FiefAuth(fief, get_authorization_scheme_token())
app = Flask(__name__)
145 def __init__( 146 self, 147 client: Fief, 148 token_getter: TokenGetter, 149 *, 150 get_userinfo_cache: Optional[UserInfoCacheGetter] = None, 151 set_userinfo_cache: Optional[UserInfoCacheSetter] = None, 152 ) -> None: 153 """ 154 :param client: Instance of a `fief_client.Fief` client. 155 :param token_getter: Function to retrieve a token. 156 It should follow the `TokenGetter` type. 157 :param get_userinfo_cache: Optional function to retrieve user information from a cache. 158 Otherwise, the Fief API will always be reached when requesting user information. 159 It should follow the `UserInfoCacheGetter` type. 160 :param set_userinfo_cache: Optional function to store user information in a cache. 161 It should follow the `UserInfoCacheSetter` type. 162 """ 163 self.client = client 164 self.token_getter = token_getter 165 self.get_userinfo_cache = get_userinfo_cache 166 self.set_userinfo_cache = set_userinfo_cache
Parameters
- client: Instance of a
fief_client.Fief
client. - token_getter: Function to retrieve a token.
It should follow the
TokenGetter
type. - get_userinfo_cache: Optional function to retrieve user information from a cache.
Otherwise, the Fief API will always be reached when requesting user information.
It should follow the
UserInfoCacheGetter
type. - set_userinfo_cache: Optional function to store user information in a cache.
It should follow the
UserInfoCacheSetter
type.
168 def authenticated( 169 self, 170 *, 171 optional: bool = False, 172 scope: Optional[list[str]] = None, 173 acr: Optional[FiefACR] = None, 174 permissions: Optional[list[str]] = None, 175 ): 176 """ 177 Decorator to check if a request is authenticated. 178 179 If the request is authenticated, the `g` object will have an `access_token_info` property, 180 of type `fief_client.FiefAccessTokenInfo`. 181 182 :param optional: If `False` and the request is not authenticated, 183 a `FiefAuthUnauthorized` error will be raised. 184 :param scope: Optional list of scopes required. 185 If the access token lacks one of the required scope, a `FiefAuthForbidden` error will be raised. 186 :param acr: Optional minimum ACR level required. 187 If the access token doesn't meet the minimum level, a `FiefAuthForbidden` error will be raised. 188 Read more: https://docs.fief.dev/going-further/acr/ 189 :param permissions: Optional list of permissions required. 190 If the access token lacks one of the required permission, a `FiefAuthForbidden` error will be raised. 191 192 **Example** 193 194 ```py 195 @app.get("/authenticated") 196 @auth.authenticated() 197 def get_authenticated(): 198 return g.access_token_info 199 ``` 200 """ 201 202 def _authenticated(f): 203 @wraps(f) 204 def decorated_function(*args, **kwargs): 205 token = self.token_getter() 206 if token is None: 207 if optional: 208 g.access_token_info = None 209 return f(*args, **kwargs) 210 raise FiefAuthUnauthorized() 211 212 try: 213 info = self.client.validate_access_token( 214 token, 215 required_scope=scope, 216 required_acr=acr, 217 required_permissions=permissions, 218 ) 219 except (FiefAccessTokenInvalid, FiefAccessTokenExpired) as e: 220 if optional: 221 g.access_token_info = None 222 return f(*args, **kwargs) 223 raise FiefAuthUnauthorized() from e 224 except ( 225 FiefAccessTokenMissingScope, 226 FiefAccessTokenACRTooLow, 227 FiefAccessTokenMissingPermission, 228 ) as e: 229 raise FiefAuthForbidden() from e 230 231 g.access_token_info = info 232 233 return f(*args, **kwargs) 234 235 return decorated_function 236 237 return _authenticated
Decorator to check if a request is authenticated.
If the request is authenticated, the g
object will have an access_token_info
property,
of type fief_client.FiefAccessTokenInfo
.
Parameters
- optional: If
False
and the request is not authenticated, aFiefAuthUnauthorized
error will be raised. - scope: Optional list of scopes required.
If the access token lacks one of the required scope, a
FiefAuthForbidden
error will be raised. - acr: Optional minimum ACR level required.
If the access token doesn't meet the minimum level, a
FiefAuthForbidden
error 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
FiefAuthForbidden
error will be raised.
Example
@app.get("/authenticated")
@auth.authenticated()
def get_authenticated():
return g.access_token_info
239 def current_user( 240 self, 241 *, 242 optional: bool = False, 243 scope: Optional[list[str]] = None, 244 acr: Optional[FiefACR] = None, 245 permissions: Optional[list[str]] = None, 246 refresh: bool = False, 247 ): 248 """ 249 Decorator to check if a user is authenticated. 250 251 If the request is authenticated, the `g` object will have a `user` property, 252 of type `fief_client.FiefUserInfo`. 253 254 :param optional: If `False` and the request is not authenticated, 255 a `FiefAuthUnauthorized` error will be raised. 256 :param scope: Optional list of scopes required. 257 If the access token lacks one of the required scope, a `FiefAuthForbidden` error will be raised. 258 :param acr: Optional minimum ACR level required. 259 If the access token doesn't meet the minimum level, a `FiefAuthForbidden` error will be raised. 260 Read more: https://docs.fief.dev/going-further/acr/ 261 :param permissions: Optional list of permissions required. 262 If the access token lacks one of the required permission, a `FiefAuthForbidden` error will be raised. 263 :param refresh: If `True`, the user information will be refreshed from the Fief API. 264 Otherwise, the cache will be used. 265 266 **Example** 267 268 ```py 269 @app.get("/current-user") 270 @auth.current_user() 271 def get_current_user(): 272 user = g.user 273 return f"<h1>You are authenticated. Your user email is {user['email']}</h1>" 274 ``` 275 """ 276 277 def _current_user(f): 278 @wraps(f) 279 @self.authenticated( 280 optional=optional, scope=scope, acr=acr, permissions=permissions 281 ) 282 def decorated_function(*args, **kwargs): 283 access_token_info: Optional[FiefAccessTokenInfo] = g.access_token_info 284 285 if access_token_info is None and optional: 286 g.user = None 287 return f(*args, **kwargs) 288 289 assert access_token_info is not None 290 291 userinfo = None 292 if self.get_userinfo_cache is not None: 293 userinfo = self.get_userinfo_cache(access_token_info["id"]) 294 295 if userinfo is None or refresh: 296 userinfo = self.client.userinfo(access_token_info["access_token"]) 297 298 if self.set_userinfo_cache is not None: 299 self.set_userinfo_cache(access_token_info["id"], userinfo) 300 301 g.user = userinfo 302 303 return f(*args, **kwargs) 304 305 return decorated_function 306 307 return _current_user
Decorator to check if a user is authenticated.
If the request is authenticated, the g
object will have a user
property,
of type fief_client.FiefUserInfo
.
Parameters
- optional: If
False
and the request is not authenticated, aFiefAuthUnauthorized
error will be raised. - scope: Optional list of scopes required.
If the access token lacks one of the required scope, a
FiefAuthForbidden
error will be raised. - acr: Optional minimum ACR level required.
If the access token doesn't meet the minimum level, a
FiefAuthForbidden
error 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
FiefAuthForbidden
error 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")
@auth.current_user()
def get_current_user():
user = g.user
return f"<h1>You are authenticated. Your user email is {user['email']}</h1>"
Base error for FiefAuth integration.
49class FiefAuthForbidden(FiefAuthError): 50 """ 51 Request forbidden error. 52 53 This error is raised when using the `authenticated` or `current_user` decorator 54 but the access token doesn't match the list of scopes, permissions or minimum ACR level. 55 56 You should implement an `errorhandler` to define the behavior of your server when 57 this happens. 58 59 **Example:** 60 61 ```py 62 @app.errorhandler(FiefAuthForbidden) 63 def fief_forbidden_error(e): 64 return "", 403 65 ``` 66 """
Request forbidden error.
This error is raised when using the authenticated
or current_user
decorator
but the access token doesn't match the list of scopes, permissions or minimum ACR level.
You should implement an errorhandler
to define the behavior of your server when
this happens.
Example:
@app.errorhandler(FiefAuthForbidden)
def fief_forbidden_error(e):
return "", 403
Type of a function that can be used to retrieve a token.
Type of a function that can be used to retrieve user information from a cache.
Read more: https://docs.fief.dev/integrate/python/flask/#web-application-example
Type of a function that can be used to store user information in a cache.
Read more: https://docs.fief.dev/integrate/python/flask/#web-application-example