Package: tasos-apiauth
Tasos API Authentication LibraryA re-usable library that implements authentication, users, groups and permission handling for FastAPI. This library
is meant to allow a FastAPI developer to get up and running quickly with functions to register, authenticate and manage
groups and permissions for a new web application. It currently supports JSON Web Tokens (JWT) for authentication and
depends onSqlAlchemyfor database management and access. This library is written using
asyncio.NOTE: This is a work in progress and is not ready for production use.MotivationI wanted to create a library that would allow me to quickly setup authentication and authorization for a new web
application. I also wanted to test outGitHub Copilot- this code and portions of
the README were generated with Copilot's AI assistance.InstallationThe latest stable version can be found on pypi.org and can be installed with the package manager of your choice, e.g.
for Poetry:poetryaddtasos-apiauthQuick StartPre-requisites:Python 3.11+Poetryfor package managementAlembicfor database migrationsaiosqliteor your async database of choice (any SQLAlchemy supported
database should work)Uvicornfor running the appTo try this library out in its current state, usePoetryto install the project as a
dependency in your own project, e.g. you can run the following:poetryaddhttps://github.com/tristeng/tasos-apiauth.gitCreate a .env file in the root of your project and fill in the values (or set them in your environment):APIAUTH_SECRET_KEY=***REDACTED***
APIAUTH_ALGORITHM=HS256
APIAUTH_ACCESS_TOKEN_EXPIRE_MINUTES=15
APIAUTH_DATABASE_URL=sqlite+aiosqlite:///./demo.db?check_same_thread=falseSeetasos/apiauth/config.pyfor more configuration options and their defaults.You can generate your secret key with the following command:generatewithopensslrand-hex32orpython-c'import secrets; print(secrets.token_hex(32))'Initialize alembic for asyncio:alembicinit-tasyncalembicEdit thealembic.inifile and set thesqlalchemy.urlto the same value asAPIAUTH_DATABASE_URLin your .env file.Edit thealembic/env.pyfile and add the following (and as you develop your app, add any other models you create)):fromtasos.apiauth.modelimportBasetarget_metadata=Base.metadataand run the migrations:alembicrevision--autogenerate-m"Initial migration"alembicupgradeheadCreate an admin user for yourself with the included CLI, and follow the prompts to choose a password:python-mtasos.apiauth.clinewuseryou@website.com--adminCreate a main.py file in the root of your project and create your FastAPI app:fromfastapiimportFastAPIfromtasos.apiauth.apiimportadd_all_endpoints_to_app# create your app as you likeapp=FastAPI()# add the endpoints to your app using the default URLsadd_all_endpoints_to_app(app)If you only want to add select endpoints with custom base URLs:fromfastapiimportFastAPIfromtasos.apiauth.apiimportadd_base_endpoints_to_app,add_user_endpoints_to_app,add_group_endpoints_to_app,\add_permission_endpoints_to_app# create your app as you likeapp=FastAPI()# add only the endpoints you want at the paths you desire (path is optional and defaults to /api/auth)add_base_endpoints_to_app(app,path="/api/auth")add_user_endpoints_to_app(app,path="/api/users")add_group_endpoints_to_app(app,path="/api/groups")add_permission_endpoints_to_app(app,path="/api/permissions")# you could also add all of them at the default paths# add_all_endpoints_to_app(app)Run the app in development mode:uvicornmain:app--reloadYou should now be able to navigate tohttp://localhost:8000/docsto see the Swagger UI and use its interactive features
to interact with the API. You can also navigate tohttp://localhost:8000/redocto see the ReDoc UI.Custom Registration FunctionsIf you want to customize the registration process, you can create your own registration functions by appending them to
the post registration hooks list. Define your function(s) to accept a database session and user object. For example:fromtasos.apiauth.modelimportUserOrmfromtasos.apiauth.api.baseimportpost_registration_hooksfromsqlalchemy.ext.asyncioimportAsyncSessionasyncdefsend_registration_email_hook(db:AsyncSession,user:UserOrm)->None:# for example, save a registration code to the database and send an email to the user with a link to confirm their# email addresspasspost_registration_hooks.append(send_registration_email_hook)PermissionsTo implement permissions, you will first need to create a StrEnum class that defines the permissions you want to use,
for example:fromenumimportStrEnumclassMyCustomPermissions(StrEnum):read="read"write="write"delete="delete"NOTE: We use a string enum instead of strings to avoid typos.At this point, you'll need to create an alembic changeset to add the permissions to the database. You can do this by:alembicrevision--autogenerate-m"Add my custom permissions"Edit the generated migration file and add the following:fromalembicimportopfromtasos.apiauth.modelimportPermissionOrmfromwhereverimportMyCustomPermissionsdefupgrade()->None:# ### commands auto generated by Alembic - please adjust! ###op.bulk_insert(PermissionOrm.__table__,[{"name":perm}forperminMyCustomPermissions])# ### end Alembic commands ###Run the migration:alembicupgradeheadAs you add more permissions, you can repeat this process - you can use the same enumeration class or create a new one.Now you can use theUserHasAllPermissionsorUserHasAnyPermissiondependency to check if a user has the permissions
you want. For example:fromfastapiimportDepends,FastAPIfromtasos.apiauth.helpersimportUserHasAllPermissions,UserHasAnyPermissionfromtasos.apiauth.apiimportadd_all_endpoints_to_appfromwhereverimportMyCustomPermissions# create your app as you likeapp=FastAPI()# add the endpoints to your app using the default URLsadd_all_endpoints_to_app(app)# you can add as many or as few permissions as you want using the list argumentsperm_checker_all=UserHasAllPermissions(MyCustomPermissions.read,MyCustomPermissions.write)perm_checker_any=UserHasAnyPermission(MyCustomPermissions.read,MyCustomPermissions.write)# now inject the dependency into your routes@app.get("/some/endpoint",dependencies=[Depends(perm_checker_all)])# user must have both read and write permissionsasyncdefsome_endpoint1()->dict[str,str]:return{"works":"wooo!"}# user must have either read or write permission@app.get("/some/other/endpoint",dependencies=[Depends(perm_checker_any)])asyncdefsome_other_endpoint1()->dict[str,str]:return{"works":"wooo!"}If the user doesn't haveALLof the permissions you specified, they will get a 403 Forbidden response.DevelopmentTo develop this library, clone the repo and install the dependencies with Poetry:poetryinstallTestingThe tests are separated into unit and integration tests. The unit tests are run against a mocked database and the
integration tests are run against a real database (SQLite). The integration tests are slower and the unit test mocking
can interfere with the integration tests, so they are separated. The integration tests are marked with the@pytest.mark.integrationdecorator and can be run separately from the unit tests.To run the unit tests:poetryrunpytest-m"not integration"To run the integration tests:poetryrunpytest-m"integration"