By Bernat Sampera 10 min read Follow:

How to add auth in your app using supertokens

Adding the option to the users to log in can be very simple or very tricky. In this post I'll show the steps to set up supertokens in a react app with fastapi (python) on the backenn. The supertokens instance will be selfhosted in coolify.

Intro

Adding the option to the users to log in can be very simple or very tricky. In this post I'll show the steps to set up auth with supertoken in a react app with fastapi (python) on the backenn.

The supertokens instance will be selfhosted in coolify.
The example will be shown for the site translateprompt.com

Creating the supertokens instance

On coolify project we’ll add a new resource, this is an instance supertokens with postgresql.

Once it’s created, you can connect to the database by going to postgres → settings → edit public port → make it publicly available.
then with the credentials from the configuration page and the url and port you should be able to connect.
Test that the supertokens instance is running by going to the link where it’s deployed, you should be able to see “Hello”.

Now we have two different resources deployed

  1. Supertokens db
    Here will all the users, permissions and everything that composes the auth be saved

  2. Supertoken instance
    This includes the logic that handles all the operations that will save the data to the DB and handles all the auth

Backend

For the backend, in this guide we will not follow the default setup from supertokens where just the password and email are used for the setup, here we’ll also require that the users enter the username where they describe. Following this code the username will be available.

As we are using python and fastapi, we’ll install the following dependency.

uv add supertokens-python

And create the following file

from fastapi import APIRouter, Depends
from supertokens_python import InputAppInfo, SupertokensConfig, init

# from supertokens_python.recipe.usermetadata.syncio import get_user_metadata
from supertokens_python.recipe import emailpassword, session
from supertokens_python.recipe.emailpassword.interfaces import (
    SignUpPostOkResult,
)
from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.framework.fastapi import verify_session
from supertokens_python.recipe.usermetadata.asyncio import (
    get_user_metadata,
    update_user_metadata,
)

router = APIRouter(prefix="/auth", tags=["auth"])


def apis_override(original_implementation: emailpassword.interfaces.APIInterface):
    # Get a reference to the original sign_up_post
    original_sign_up_post = original_implementation.sign_up_post

    # Use *args and **kwargs to be compatible with any SDK version
    async def sign_up_post(*args, **kwargs):
        # 1. Call the original implementation, passing through all arguments
        #    that were given to us. This is the key part.
        response = await original_sign_up_post(*args, **kwargs)

        # 2. Check if the sign up was successful
        if isinstance(response, SignUpPostOkResult):
            # The user was created successfully
            user_id = response.user.id

            # 3. Extract form_fields from the arguments. It is always the
            #    first positional argument (at index 0).
            form_fields = args[0]

            username = None
            for field in form_fields:
                if field.id == "username":
                    username = field.value
                    break

            if username is not None:
                # 4. Save the username in the user's metadata
                await update_user_metadata(user_id, {"username": username})

        # 5. Return the original response
        return response

    # Replace the original implementation with our overridden one
    original_implementation.sign_up_post = sign_up_post
    return original_implementation


init(
    app_info=InputAppInfo(
        app_name="TranslatePrompt",
        api_domain="<backend_url>",  # FastAPI domain
        website_domain="<frontend_url>",  # React domain
        api_base_path="/auth",
        website_base_path="/auth",
    ),
    supertokens_config=SupertokensConfig(
        connection_uri="<supertokens_instance>"  # your core instance URL
    ),
    recipe_list=[
        emailpassword.init(
            sign_up_feature=emailpassword.InputSignUpFeature(
                form_fields=[emailpassword.InputFormField(id="username")]
            ),
            override=emailpassword.InputOverrideConfig(apis=apis_override),
        ),
        session.init(),  # session management
    ],
    framework="fastapi",
)

@router.get("/user")
async def get_user_info(session: SessionContainer = Depends(verify_session())):
    user_id = session.get_user_id()
    metadata_result = await get_user_metadata(user_id)

    return {"user_id": user_id, "metadata": metadata_result.metadata}

this is the auth file I am using to handle just the auth routes, but we also have to add the middleware, this will be done on the fastapi instance.

from supertokens_python.framework.fastapi import get_middleware

app = FastAPI(
    title="TranslatePrompt",
    description="Translateprompt is a site that allows users to translate with a glossary",
    version="1.0.0",
)
app.add_middleware(get_middleware())

SuperTokens automatically exposes its auth API routes under /auth/* in your FastAPI app once you’ve added the middleware:

That means you now have endpoints like:

  • POST /auth/signin

  • POST /auth/signup

  • POST /auth/signout

  • POST /auth/session/refresh

You don’t need to implement these yourself, they are provided by SuperTokens.

IMPORTANT!!

Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.

So if you have something like this it will not work

from supertokens_python.framework.fastapi import get_middleware

app.add_middleware(get_middleware())
# Nees to be after the specific supertokens middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins="*",

This is the correct version

app.add_middleware(
    CORSMiddleware,
    allow_origins="<FRONTEND_URL>",

Test the backend

You can test it by running the following curl request

curl -i -X POST http://<backend_url>/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"formFields":[{"id":"email","value":"test@example.com"},{"id":"password","value":"mypassword1"}]}'

You should receive something like this and see the user added in the database under the column emailpassword_users

{"status":"OK","user":{"id":"43466338-87be-40ac-b1a2-0a97e...

Frontend

First we’ll have to initialise the supertokens instance

SuperTokens.init({
  appInfo: {
    // learn more about this on https://supertokens.com/docs/references/frontend-sdks/reference#sdk-configuration
    appName: "TranslatePrompt",
    apiDomain: "<backend_url>",
    websiteDomain: "<frontend_url>",
    apiBasePath: "/auth", # (backend) By default, the frontend SDK queries {apiDomain}/auth/*. If you want to change the /auth to something else, then you must set this value
    websiteBasePath: "/auth" # (frontend) By default, the login UI appears on {websiteDomain}/auth. Other authentication-related user interfaces appear on {websiteDomain}/auth/*.

  },
  recipeList: [
    EmailPasswordReact.init({
      signInAndUpFeature: {
        signUpForm: {
          formFields: [
            {
              id: "username",
              label: "Username",
              placeholder: "username"
            }
          ]
        }
      }
    }),
    SessionReact.init()
  ]
});

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <SuperTokensWrapper>
      <App />
    </SuperTokensWrapper>
  </StrictMode>
);

We’ll also create an auth.ts hook to be able to know if the user is logged from any component in the frontend.

useAuth.ts

// hooks/useAuth.ts

import {useEffect, useState} from "react";
import {
  signOut,
  useSessionContext
} from "supertokens-auth-react/recipe/session";

import axiosInstance from "@/api/axiosConfig";

const BASE_URL = import.meta.env.VITE_BACKEND_URL;
const AUTH_BASE_URL = `${BASE_URL}/auth`;

export function useAuth() {
  const session = useSessionContext() as any;

  const [userData, setUserData] = useState({
    username: null,
    loading: false,
    error: null as any
  });

  useEffect(() => {
    // This flag prevents state updates if the component unmounts
    // during the async API call.
    let isMounted = true;

    const fetchUserData = async () => {
      // Only fetch if we have a confirmed session and user ID,
      // and we aren't already fetching.
      if (session.doesSessionExist && session.userId) {
        setUserData({username: null, loading: true, error: null});
        try {
          const response = await axiosInstance.get(`${AUTH_BASE_URL}/user`);
          if (isMounted) {
            setUserData({
              username: response.data.metadata.username,
              loading: false,
              error: null
            });
          }
        } catch (error) {
          console.error("Failed to fetch user data:", error);
          if (isMounted) {
            setUserData({username: null, loading: false, error: error});
          }
        }
      }
    };
    console.log("fetchUserData", fetchUserData);

    fetchUserData();

    return () => {
      isMounted = false;
    };
    // This effect should ONLY re-run when the user's session status or ID changes.
  }, [session.doesSessionExist, session.userId]);

  const handleLogout = async () => {
    try {
      await signOut();
      window.location.href = "/";
    } catch (error) {
      console.error("Logout error:", error);
    }
  };

  // The final loading state is true if the session is loading OR our user data is loading.
  const isLoading = session.loading || userData.loading;

  return {
    loading: isLoading,
    loggedIn: session.doesSessionExist,
    logout: handleLogout,
    userId: session.userId,
    userName: userData.username,
    error: userData.error // Expose error state for UI to optionally use
  };
}

Then this can be used like this

  const {loggedIn, userName} = useAuth();
  return (
    <div className="max-w-7xl mx-auto py-8">
      {loggedIn && <p>Welcome {userName}</p>}

Let's connect !!

Get in touch if you want updates, examples, and insights on how AI agents, Langchain and more are evolving and where they’re going next.