Example
Example¶
Lets see a little example of how to use the middleware, and Integrate it with the application.
We could Create Our Project based on Starlette, instead of FastAPI.
Create Crud Instance¶
Using SQLAlchemy, we can create a Crud instance, and we can use it to create a
Database and show multiple crud functionalities.
import typing
from sqlalchemy import JSON, Column, MetaData, String, Table, create_engine
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import create_session
from starlette.config import Config
config = Config(".env.sample")
class DB:
    def __init__(self) -> None:
        self._engine = create_engine(config("DATABASE_URL"))
        _metatable = Table(
            "users",
            MetaData(bind=self._engine),
            *[
                Column("id", String, primary_key=True, nullable=False),
                Column("token", JSON),
            ],
        )
        _metatable.create(checkfirst=True)
        Base = automap_base(metadata=_metatable.metadata)
        Base.prepare()
        self._session = create_session(bind=self._engine)
        self._User = Base.classes.users
- Here we are using the create_enginefunction to create a database engine, and we are using theconfigfunction to get the database url from the.env.samplefile.
- We are using the Tablefunction to create a table, and we are using theColumnfunction to create the columns of the table.
- We are using the create_sessionfunction to create a session, and we are using theBasefunction to create the Base class.
- We are using the preparefunction to prepare the Base class.
    def put(self, user_id: str, token: typing.Dict[str, typing.Any]) -> None:
        self._session.merge(self._User(id=user_id, token=dict(token)))
        self._session.flush()
- Here we are using the mergefunction to merge the user, and we are using theflushfunction to flush the session.
- We are using the idandtokenattributes to create the user.
- We are using the putfunction to put the user in the database.
    def get(self, user_id: str) -> typing.Optional[typing.Dict[str, typing.Any]]:
        result = (
            self._session.query(self._User).filter(self._User.id == user_id).first()
        )
        if result is not None:
            return result.token
- Here we are using the queryfunction to query the database, and we are using thefilterfunction to filter the query, and we are using thefirstfunction to get the first result of the query.
- We are using the idattribute to filter the query, and we are using thetokenattribute to get the token of the user.
- We are using the getfunction to get the token of the user.
    def delete(self, user_id: str) -> None:
        self._session.query(self._User).filter(self._User.id == user_id).delete()
        self._session.flush()
- Here we are using the queryfunction to query the database, and we are using thefilterfunction to filter the query, and we are using thedeletefunction to delete the user.
- We are using the idattribute to filter the query, and we are using theflushfunction to flush the session.
- We are using the deletefunction to delete the user.
Create Middleware & App¶
Now After initializing the database, we can create the middleware and the app.
Let's Call our Imports and Initial the Environment Variables using the Config
function in starlette.config.
from starlette.applications import Starlette
from starlette.config import Config
from starlette.datastructures import Secret
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import JSONResponse
from authx import MiddlewareOauth2
config = Config(".env.sample")
- Here we are using the Configfunction to get the environment variables from the.env.samplefile.
config.SECRET_KEY = config("SECRET_KEY", cast=Secret)
config.SERVER_METADATA_URL = config("SERVER_METADATA_URL", cast=str)
config.CLIENT_ID = config("CLIENT_ID", cast=str)
config.CLIENT_SECRET = config("CLIENT_SECRET", cast=Secret)
- Here we are using the SECRET_KEYandSERVER_METADATA_URLenvironment variables to set theSECRET_KEYandSERVER_METADATA_URLattributes of theconfigfunction.
- We are using the CLIENT_IDandCLIENT_SECRETenvironment variables to set theCLIENT_IDandCLIENT_SECRETattributes of theconfigfunction.
app = Starlette()
db = DB()
class AuthenticateMiddleware(MiddlewareOauth2):
    PUBLIC_PATHS = {"/public"}
app.add_middleware(
    AuthenticateMiddleware,
    db=db,
    server_metadata_url=config.SERVER_METADATA_URL,
    client_id=config.CLIENT_ID,
    client_secret=config.CLIENT_SECRET,
    force_https_redirect=False,
)
app.add_middleware(SessionMiddleware, secret_key=config.SECRET_KEY)
- We Instance the application using the Starlettefunction, and we are using thedbandserver_metadata_urlattributes of theconfigfunction to initialize thedbandserver_metadata_urlattributes of theAuthenticateMiddlewareclass.
- We are using the client_idandclient_secretattributes of theconfigfunction to initialize theclient_idandclient_secretattributes of theAuthenticateMiddlewareclass.
- We are using the force_https_redirectattribute of theconfigfunction to initialize theforce_https_redirectattribute of theAuthenticateMiddlewareclass.
- We are using the add_middlewarefunction to add theSessionMiddlewareclass to the application.
@app.route("/other")
async def homepage(request):
    user = request.session.get("user")
    return JSONResponse(user)
- Here we are using the routefunction to create a route, and we are using thegetfunction to get the user from the session.
- We are using the JSONResponsefunction to return the user as a JSON response.
@app.route("/public")
async def homepage(request):
    user = request.session.get("user")
    payload = {"message": "User not authenticated"} if user is None else user
    return JSONResponse(payload)
- Here we are using the routefunction to create a route, and we are using thegetfunction to get the user from the session.
- We are using the payloadvariable to create the payload of the response.
- We are using the JSONResponsefunction to return the payload as a JSON response.
To ensure that we can run the app we will use uvicorn and httpx to run the
app.
if __name__ == "__main__":
    import logging
    import sys
    import uvicorn
    logger = logging.getLogger("httpx")
    logger.setLevel(logging.DEBUG)
    logger.addHandler(logging.StreamHandler(sys.stdout))
    uvicorn.run(app, host="localhost", port=5001)
- Here we are using the ifstatement to check if the__name__is equal to__main__.
- We import some libraries like loggingandsys, and at the end we are using theuvicornfunction to run the app.
- We are using the getLoggerfunction to get the logger of thehttpxlibrary, and we are using thesetLevelfunction to set the level of the logger toDEBUG.
- We are using the addHandlerfunction to add theStreamHandlerto the logger.
- We are using the runfunction to run the app.
Result¶
At the end of the process, we can run the app using the following command:
python -m app
We can see that the app is running on the following URL:
- When you visit http://localhost:5001/public, you will see that you are not authenticated.
- When you visit http://localhost:5001/other, you will be redirected to your tenant, to authenticate.
- Once authenticated, you will be redirected back to
  http://localhost:5001/other, and your email will appear.