Auth0 is an identity management service that provides a secure and scalable backend for storing and managing user data.

This means that if you're running a SaaS business, instead of hosting all your (potentially sensitive) user details yourself, you host it all with Auth0. This way you can focus on the core business logic of your product while knowing that sensitive user details are secure. In addition, you can enable features such as user registration, password-less login, SMS login, or social login within a few clicks, which reduces your development time massively.

In this blog post we'll show you how to use Auth0 inside a Starlette application.

Auth0 Setup

While Auth0 is very powerul, the setup process does require configuring a few bits. Let's go through them one by one.

0. Signup

The first step is of course to register for an Auth0 account. You can do this at their signup page.

1. Create a Tenant

The next step is to create a Tenant. A Tenant is where you configure your use of Auth0. In practice, you would create individual tenants for each of your application environments (staging, production, etc.). This helps keep the staging users isolated from production users.

Follow the steps documented here to create a tenant: https://auth0.com/docs/get-started/auth0-overview/create-tenants.

2. Create an Application

Next, create an Application in your account. An Application on Auth0 corresponds to your own software that uses Auth0 for user management.

There are multiple types of applications you can create. For the purpose of this blog post we'll create what's called a "Regular Web Application" that can be used with Python applications running on the backend.

Follow the steps documented here to create an application: https://auth0.com/docs/get-started/auth0-overview/create-applications.

3. Configure the Application

After creating an application, you need to configure a few URLs for the application you just created. These are mainly the URLs used as redirect URLs between your application and Auth0 to have a working authentication flow.

In your application settings, configure the following two URLs:

  1. Allowed Callback URLs: set this to http://localhost:9001/auth/callback
  2. Allowed Logout URLs: set this to http://localhost:9001

We're assuming here that our local Starlette server will run on port 9001. Feel free to replace it with whatever port you've chosen for your own local development instance. Later on, when you're deploying your application to production, you would replace http://localhost:9001 with the production URL of your application.

Auth0 + Starlette

Now that a basic Auth0 configuration is in place, let's write a minimal Starlette application to make use of this. Let's start with defining the configuration options we need to be able to communicate with Auth0.

from starlette.config import Config

config = Config(".env")

AUTH0_DOMAIN = config("AUTH0_DOMAIN")

AUTH0_CLIENT_ID = config("AUTH0_CLIENT_ID")

AUTH0_CLIENT_SECRET = config("AUTH0_CLIENT_SECRET")

All this looks fairly straight-forward so far.

The AUTH0_DOMAIN is the unique domain assigned by Auth0 to your application and the other two configuration options are client keys. The values for all these configuration options are available on your Auth0 application's settings page.

Next, we need to initialise an OAuth connector that talks to Auth0. We'll make use of the authlib library to do this.

from authlib.integrations.starlette_client import OAuth

oauth = OAuth()

oauth.register(
    "auth0",
    client_id=AUTH0_CLIENT_ID,
    client_secret=AUTH0_CLIENT_SECRET,
    client_kwargs={
        "scope": "openid profile email",
    },
    server_metadata_url=(
        f"https://{AUTH0_DOMAIN}/.well-known/openid-configuration"
    ),
)

This snippet sets up an OAuth client object that is able to communicate with the Auth0 server. We'll make use of this object in our HTTP endpoints.

We're now ready to start defining our HTTP endpoints. Let's start by defining a simple login endpoint.

from starlette.endpoints import HTTPEndpoint
from starlette.requests import Request

class LoginEndpoint(HTTPEndpoint):
    async def get(self, request: Request):
        return await oauth.auth0.authorize_redirect(
            request, str(request.url_for("auth:callback"))
        )

class CallbackEndpoint(HTTPEndpoint):
    async def get(self, request: Request):
        token = await oauth.auth0.authorize_access_token(request)

        user_info = token.get("userinfo")
        if user_info:
            request.session["AUTH0_USER"] = user_info["sub"]

        return RedirectResponse("/")

Let's quickly go through both the endpoints.

In the LoginEndpoint, we're redirecting the user to Auth0 where they can complete the login process. And in case they don't have an account with Auth0 yet, Auth0 will prompt them to create one and then login. So this LoginEndpoint actually also acts as a registration endpoint.

The CallbackEndpoint is what handles the routing for /auth/callback. After a user has logged themselves in on Auth0, Auth0 redirects them to this URL (which we configured in the previous section as a callback URL). The HTTP endpoint handling this URL extracts the access token that Auth0 sent in its redirect, extracts a user ID (assigned by Auth0) contained in the userinfo field inside the token, and sets it as a session cookie. Finally, the user is redirected to the index page. We chose the index page because in the previous step we added / to the list of Allowed Logout URLs.

And that's the entire login process!

Let's quickly finish up the loop by writing a LogoutEndpoint.

from urllib.parse import urlencode, quote_plus

from starlette.endpoints import HTTPEndpoint
from starlette.requests import Request
from starlette.responses import RedirectResponse

class LogoutEndpoint(HTTPEndpoint):
    async def get(self, request: Request):
        if "AUTH0_USER" in request.session:
            del request.session["AUTH0_USER"]

        params = urlencode(
            {
                "returnTo": str(request.url_for("index")),
                "client_id": AUTH0_CLIENT_ID,
            },
            quote_via=quote_plus,
        )

        return RedirectResponse(f"https://{AUTH0_DOMAIN}/v2/logout?{params}")

We do two things in this endpoint: clear our own session cookie and then redirect the user to Auth0 so that Auth0 can log the user out on their own end. This way we can be sure that the user is really completely logged out.

The only remaining bit is to hook these endpoints up to a Starlette application instance:

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middlewares.sessions import SessionMiddleware

instance = Starlette(
    middleware=[
        Middleware(SessionMiddleware, secret_key="secret"),
    ],
    routes=(
        Route("/", IndexEndpoint, name="index"),
        Mount(
            "/auth",
            routes=(
                Route("/login", LoginEndpoint, name="auth:login"),
                Route("/callback", CallbackEndpoint, name="auth:callback"),
                Route("/logout", LogoutEndpoint, name="auth:logout"),
            ),
        ),
    ),
)

And there we have it! In less than 50 lines of code, our application has the ability to register users, log them in/out, send forgot password emails, and hundreds of other features than Auth0 provides in a secure manner out of the box.

Conclusion

In this blog post we looked at how to use Auth0 as the identity provider for Starlette applications.

In one of the next blog posts we'll look at how to extend this integration using Auth0's powerful management API which can unlock even more features. Until then, happy building! 🏗️

All code you see in this article is freely available on our Github: https://github.com/geniepy/snippets/tree/main/blog/starlette-auth0.


Photo by Micah Williams on Unsplash

Launch your next SaaS quickly using Python 🐍

User authentication, Stripe payments, SEO-optimized blog, and many more features work out of the box on day one. Simply download the codebase and start building.