Package: web-auth-sdk
web-auth-sdkThe web-auth-sdk is an authorization SDK that is used to build protected Web APIs.
It provides the ability to intercept incoming requests and inject custom logic for authentication and authorization
before the request reaches the view function.In addition, it supports Google OAuth2 for logging in and session logging in.To access protected APIs, clients should authenticate by passing authorizations. For example, a JWT key can be used as follows:curl'http://api.example.com/resources'-H'Authorization: Bearer eyJ1c2VyX2lkIjoxLCJwZXJtaXNzaW9uX2JpdG'curl'http://api.example.com/resources?access_token=eyJ1c2VyX2lkIjoxLCJwZXJtaXNzaW9uX2JpdG'curl'http://api.example.com/resources'--cookie'access_token=eyJ1c2VyX2lkIjoxLCJwZXJtaXNzaW9uX2JpdG'TIP:When utilizing FastAPI, click the lock symbol on Swagger UI to include your JWT.
Runmake startupfor a quick preview.RequirementsPython 3.8+FastAPI 0.80+ (recommended)Django 4.0+ (optional)Flask 2.0+ (optional)Installationpipinstallweb-auth-sdkorgitclonehttps://github.com/yangaound/web-auth-sdkcdweb-auth-sdk&&poetryinstallPermission RepresentationPermission list, located atusr/etc/permissions.jsonfile:permissions=[{'bitmask_idx':0,'codename':'add_order','name':'Can add order','service':'order'},{'bitmask_idx':1,'codename':'change_order','name':'Can change order','service':'order'},{'bitmask_idx':2,'codename':'delete_order','name':'Can delete order','service':'order'},{'bitmask_idx':3,'codename':'view_order','name':'Can view order','service':'order'},{'bitmask_idx':4,'codename':'add_tickettype','name':'Can add ticket type','service':'order'},{'bitmask_idx':5,'codename':'change_tickettype','name':'Can change ticket type','service':'order'},{'bitmask_idx':6,'codename':'view_tickettype','name':'Can view ticket type','service':'order'},{'bitmask_idx':7,'codename':'delete_tickettype','name':'Can delete ticket type','service':'order'},]How to grant permissions?Permissions are encoded using a bitmask of length n that is a multiple of 24.
Each permission is represented by a 1 on the correspondingbitmask_idx-th position in the bitmask, indicating that
the permission is granted.Base64-encoded the bitmaskBitmaskBase64-encoded111111111111111111111111111111110111111101111111/////39/Decoded/Encoded JWT{"user_id":1,"permission_bitmask":"/////39/","iat":1678798980,"exp":1678800187}eyJ1c2VyX2lkIjoxLCJwZXJtaXNzaW9uX2JpdG1hc2siOiIvLy8vLzM5LyIsImlhdCI6MTY3ODc5ODk4MCwiZXhwIjoxNjc4ODAwMTg3fQDevelopmentFastAPIimportweb_auth@fastapi.get('/tickets')@web_auth.permissions('view_ticket')# Iterable[str] are acceptableasyncdeflist_tickets()->list:return[]Djangoimportweb_authfromweb_auth.djangoimportDjangoBridgeweb_auth.configure(bridge_class=DjangoBridge)@web_auth.permissions('view_ticket')deflist_tickets(request):passurlpatterns=[django.urls.path('list-tickets',list_tickets)]Flaskimportweb_authfromweb_auth.flaskimportFlaskBridgeweb_auth.configure(bridge_class=FlaskBridge)@flask.route('/tickets',methods=['GET'])@web_auth.permissions('view_ticket')deflist_tickets()->list:return[]Use instanced contextimportweb_authcontext=web_auth.make_context(bridge_class='web_auth.fastapi.FastapiBridge')@fastapi.get('/tickets')@context.permissions('view_ticket')asyncdeflist_tickets()->list:return[]Retrieve the consumerimportfastapiimportweb_auth@fastapi.get('/profile')@web_auth.permissions(['view_directory'])defget_profile(consumer:web_auth.Consumer)->dict:return{'user':consumer.user.dict(),'directories':'get_directories(consumer.user.user_id)',}Implement Fine-Grained Permission Controlimportfastapiimportweb_auth@fastapi.post('/some-action')defsome_action(request:fastapi.Request):# Create a context with fastapi bridge classcontext=web_auth.make_context(bridge_class=web_auth.Config.DEFAULT_BRIDGE_CLASS)# Authorize access with specific permissions (e.g., 'view_directory')# If this request lacks permission, it will raise `web_auth.AuthException`_:web_auth.Consumer=context.bridge.access_control(request=request,permissions={'view_directory'},aggregation_type=web_auth.PermissionAggregationTypeEnum.ALL,)# Do some actionCustomizationPermission StoragefromtypingimportOptionalimportfastapiimportrequestsfromweb_authimportmake_context,Storage,PermissionModel,ContextclassRESTStorage(Storage):def__init__(self,ttl:int,url:str,context:Optional[Context]=None):self.url=urlsuper().__init__(ttl=ttl,context=context)def_load_permissions(self)->list[PermissionModel]:return[PermissionModel(**r)forrinrequests.get(self.url).json()]my_context=make_context(storage_class=RESTStorage,storage_params={'ttl':60,'url':'http://api.example.com/permissions?format=json'},)@fastapi.get('/tickets')@my_context(['view_ticket','change_ticket'])defget_tickets()->list[object]:passAuthentication and Authenticated Consumer/Userimportpydanticimportfastapifromweb_authimportmake_context,Consumerfromweb_auth.fastapiimportFastapiBridgeclassAuthenticatedUser(pydantic.BaseModel):account:strclassMyFastapiBridge(FastapiBridge):# Inject your consumer here if it's not inherited from the `web_auth.Consumer`consumer_class=Consumerdefauthenticate(self,request:fastapi.Request)->Consumer:# Your authenticate logic herereturnConsumer(permission_bitmask='11101101111',user=AuthenticatedUser(account='52354342/Jack'),)my_context=make_context(bridge_class=MyFastapiBridge)@fastapi.get('/me')@my_context([])defget_profile(consumer:Consumer)->AuthenticatedUser:returnconsumer.userAuthorizationimportfastapifromweb_authimportmake_context,BitmaskAuthorization,Consumer,PermissionAggregationTypeEnumfromweb_auth.fastapiimportFastapiBridgeclassMyAuthorization(BitmaskAuthorization):defauthorize(self,consumer:Consumer,permissions:set[str],aggregation_type:PermissionAggregationTypeEnum,):permission_models=self.context.storage.get_permissions()# Checks whether the `consumer` has the `permissions` in `permission_models`classMyFastapiBridge(FastapiBridge):# Inject your Authorization implementation here, it's default to BitmaskAuthorization.authorization_class=MyAuthorization# Configurate your customizationmy_context=make_context(bridge_class=MyFastapiBridge)@fastapi.get('/tickets')@my_context(['view_ticket','change_ticket'])defget_tickets()->list[object]:passOauth2 clientInstall apps tosettings.pyINSTALLED_APPS=['web_auth.django']Register url tourls.pyurlpatterns=[django.urls.path('',django.urls.include('web_auth.django.urls')),]Login with Googlehttp://api.example.com/google/loginhttp://api.example.com/google/authSession Loginhttp://api.example.com/session/csrftokenhttp://api.example.com/session/login