logo
  • Home

  • Blog

  • Projects

  • Library

  • About

PF project: Authentification

This post is intended as a personal exploration of various software patterns, rather than a guide.

What do we working with?

  • Client - Next JS
  • Server - Pyhton FastApi

Our backend using JWT tokens as an access token with rotation of refresh tokens.

Libraries

On the clinet side I use Tanstack React Query to fetch and cashe user session data

  1. React-Query - elegant layer for everething related to fetching and cashing Everything is intuitive and handled with a very little amount of code.
  2. Axios for http requests - cleaner and nicely combines with React-useQuery

Approach

To fetch and cache session information, I divided API calls in 2 categories:

  1. Actions - API calls that does not have an effect on cached entity data.
  2. Custom hooks - React-useQuery API calls that influences a cached entity in-app.

Action example

authActions.ts
  async function login(credentials){
    try {
      const { data, status } = await apiLoginUser(credentials);
      presistTokens(data.tokens)
      return { status: status };

    } catch (error) {
      hanldeError(error.response.status )
      return { status: error.response.status };
    }
  }

Custom hook example

useAuth.tsx
function useAuth(){
  return useQuery('user', getSession, {
    enabled: false, // needed to handle refetch manually
    retry: false,
    staleTime: 60 * (60 * 1000), // 60 min
    onSuccess: (data) => {
      /* success callback */
    },
    onError: () => {
      /* error callback */
    },
    /* `select` will be returned `data` when accessing query results */
    select: (data) => formatResponse(data),
  });
};

Flow

Each time the Client will make a request for protected page/data - we do request on the Server. No need to store User data in React state.

Login pattern:

  1. Our login(action) send user credentials and receives JWT.
  2. Short-lived JWT is encoded and stored and being sent with each protected API call.
  3. On successful login, we trigger re-fetching in custom hook useAuth()
  4. Each component that depends on the user session will be updated based on new useAuth() query result

Components examples

Login.tsx
function Login(){
    ...
    const { refetch } = useAuth()
 
    async function onLoginSubmit(e: FormEvent<HTMLFormElement>) {
        e.preventDefault();

        const { status } = await login({ username, password });

        if (status === 200) {
          /* trigger user refetch to update cashed user */
          refetch();
        }
    }

    return (
        <form onSubmit={onLoginSubmit}>
            ...
        </form>
    )
}
Navbar.tsx
function Navbar() {
  const { data: user, isLoading } = useAuth();

  return (
    <nav >
      ...
      { user ? <AppLink /> : <LoginButton />}
      ...
    </nav>
  );
}