Skip to content

Authentication

Shu-Wei Hsu edited this page Jan 2, 2025 · 2 revisions

Authentication

We will talk about

  • the service and library involve in auth flow
  • the steps of the auth flow
  • the term we invented and used ("RecNet JWT", "Firebase JWT")
  • roles, user status

Auth provider & library

We use Firebase Auth as our Auth provider to enable login via Google (currently only Google, but we can enable login via other account like GitHub, Facebook in the future by configuring at Firebase console).

Instead of directly using Firebase's library firebase/auth, we use library next-firebase-auth-edge, since it integrates with Next.js better, to help us perform operation like login, logout, get/decode user cookie server side. This tool basically creates a middleware and two api endpoints to handle login and logout. And we can also use APIs of this library to get token(from cookie) or decoded token, which contains some user information, at server side.

We recommend go through the following docs and example to understand how it works with Next.js.

Ref:

Auth flow

First, we have these components/roles participating in the authentication flow:

  1. Auth server (Firebase Auth): it handles authentication and issues ID token to our application (recnet) after authentication
  2. Application server (recnet): it is the server(web app) that initializes login or logout (use next-firebase-auth-edge). And this server consumes resources(APIs, data) from recnet-api.
  3. Resource server (recnet-api): it connects with our database and serves data through API endpoints. Most of the API endpoints are protected, which means we would need an access token (JWT) to call the API. (Please refer to recnet-api docs to know which endpoints are protected by "RecNet JWT" or "Firebase JWT")

Steps

1. Login via Firebase Auth

This step involves the auth server(Firebase auth) and our application server(recnet).

After clicking the "Log In" button in the top-right corner, it will redirect you to Google login page and let you choose the account you wanna use. After the authentication is done by Firebase, it will redirect to our app with a id token and next-firebase-auth-edge will set the id token at cookie by calling endpoint api/login (you can find related code at AuthProvider.tsx). After this step, you can actually get the token via getTokens API provided by next-firebase-auth-edge to get the token (and also the decoded token). We also have an util function getTokenServerSide to do this easily.

The id token returned from Firebase contains information like user email, user id at Firebase (not user id in our app), login provider, etc. The id token merely contains information from your login provider. It doesn't include any "recnet-related" data like user id and user's role. We called this id token "Firebase JWT" and will use this token later to exchange for a access token for our backend APIs ("RecNet JWT")

Screenshot 2025-01-02 at 10 02 52 AM

2. Access resource at recnet-api

This step involves the resource server(recnet-api) and our application server(recnet).

After redirecting back to our app after the first step and setting the "Firebase-JWT" in cookies. We need to retrieve a "RecNet-JWT" which contains recnet-related information like userId and role, so that we can bring the "RecNet JWT" in future requests to recnet-api and recnet-api can identify the caller who makes the request to recnet-api and perform subsequent actions (checking permissions, getting data in DB...).

We perform this exchange steps by hitting the /users/login API endpoint at recnet-api (the actual API calling is wrapped by trpc, you can see the code at AuthProvider.tsx and find the function loginMutation, or view the login procedure at apps/recnet/src/server/routers/user.ts). We bring the Firebase JWT to hit this API and this API will find the correspond user (the person who want to login) in DB and return the recnet user information. Then, we use an util function setRecnetCustomClaims to update the id token in cookies (the "update" here makes the original "Firebase JWT" transform into "RecNet JWT").

The related code can be found at AuthProvider.tsx, after redirecting back (the first step), there is a listener onIdTokenChanged, provided by firebase/auth package, to perform the rest of the first step and the second step.

After this step, when using getTokenServerSide function, it will return a "RecNet JWT" instead of a "Firebase JWT". And we will automatically bring the RecNet JWT when we are hitting APIs of recnet-api.

We can summarize these steps in the following flow chart. Screenshot 2025-01-02 at 10 59 10 AM

Roles and user status

Roles

We have different roles currently: "USER" and "ADMIN". They have different permission for resources and pages.

Status

There are some special status for users

  1. Unregistered: If after the first step of the auth flow, we can't find user data in DB, then this user is "unregistered" which means this is the first time they logged into our app. We must redirect this user to /onboard page.

  2. Inactivated: Each user can un-activate account if they want. If after the 2nd step of the above, we find out that the user is inactivated, we must redirect the user to /reactivate page.

For more details, check the implementation of the high-order component withAuthRequired and its usage. This is the component and centralized logic that handles page protection and access control based on role and status.