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:
- Allowed Callback URLs: set this to
http://localhost:9001/auth/callback
- 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.