-
Notifications
You must be signed in to change notification settings - Fork 0
Security
This project integrates the Better-Auth library for TypeScript, which provides a secure authentication system with session management, cookie-based auth, password hashing, email verification, account linking, and more.
Our integration consists of four main parts:
-
The Database Schema (backend/src/db/schema.ts): We use the Drizzle adapter with Better-Auth tables
user,session,account, andverificationfor account and session handling. All primary keys and foreign keys for users are set to Text (UUIDs) to maintain compatibility with Better-Auth's internal logic. - The Auth Server (backend/src/lib/auth.ts): This is where the Better-Auth engine is configured, defining how the server handles credentials and session rules.
-
The Auth Client (frontend/lib/auth-client.ts): A specialized API wrapper that allows our Expo app to communicate with the backend auth endpoints using a type-safe SDK (e.g.
authClient.signIn.email()). - The Auth Middleware (backend/src/middleware/auth-middleware.ts): Custom Hono middleware that extracts Better-Auth's session cookies from incoming requests to identify and authorize users for protected routes.
Login Flow:
- Request: The Expo app sends a login request via the Auth Client.
- Processing: The Better-Auth handler on the Hono server intercepts the request, validates credentials, and creates a session.
-
Response: The Better-Auth handler sends back a
Set-Cookieheader. - Subsequent Calls: For all future API calls, the cookie is automatically attached by the native networking stack. Our Auth Middleware captures the user session on the backend to control access to protected endpoints.
More details, including security concerns and Better-Auth code implementation details are listed below...
- Never hardcode API keys, passwords, or tokens in code.
- Store secrets in environment variables.
The env.example.prod file lists important credentials, for example:
# --- Database (aero_db) ---
POSTGRES_USER=admin
POSTGRES_PASSWORD=CHANGE_ME_PASSWORD
POSTGRES_DB=aero_prod_db
DATABASE_URL=postgresql://admin:CHANGE_ME_PASSWORD@db:5432/aero_prod_db
Publicly, only placeholders are used. Locally, they are replaced with the real values. These credentials and others are never hardcoded or revealed elsewhere.
- Restrict which domains can make requests to the API.
- Specify allowed methods and headers.
CORS is configured in the backend so that only trusted origins can access the API endpoints.
app.use(
"/api/*", // Only apply CORS to the API routes
cors({
origin: [
"https://aero.com",
"http://localhost:3000", // For testing the local production build
"http://localhost:8081", // For the local Expo dev server (npm start)
],
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowHeaders: ["Content-Type", "Authorization"],
}),
);- Integrate Better-Auth, which is a well-trusted and well-tested authentication library.
- Better-Auth provides a built-in session handler to validate users.
- Backend custom middleware runs on every API request to store the user and their session.
- If a particular user is invalid, they are blocked from certain pages.
In backend/src/middleware/auth-middleware.ts, the following function stores a user and their session, and validates it on every request:
// Authentication: Stores user and session ID in context for later use. Runs on every request
export const authMiddleware = async (c: Context, next: Next) => {
const session = await auth.api.getSession({ headers: c.req.raw.headers }); // Extract session cookie
if (!session) {
// No valid session, user not logged in
c.set("user", null);
c.set("session", null);
await next();
return;
}
// Valid session, store user and session ID to make available in all subsequent routes
c.set("user", session.user);
c.set("session", session.session);
await next();
};The following function is run by default on every request so that you must be a validated user to access any endpoint:
// Authorization: Require user to be authenticated
export const requireAuth = async (c: Context, next: Next) => {
const user = c.get("user");
if (!user) {
return c.json({ error: "User not logged in" }, 401);
}
await next();
};The following function runs on public endpoints that do not require authentication such as the login page or home page:
// No authentication required for public routes
export const publicNoAuth = async (c: Context, next: Next) => {
c.set("noAuthRequired", true);
await next();
};- Set cookies with
Secureflag, meaning cookies can only be sent over HTTPS. - Expire sessions after logout or after an extended period of time (handled by Better-Auth).
- Handle cookies securely in frontend using secure storage.
In frontend/lib/auth-client.ts, the frontend implements the authClient for Expo from the Better-Auth library where cookies are handled and stored using SecureStore.
export const authClient = createAuthClient ({
baseURL: "https://localhost:3001", // Backend URL
plugins: [
expoClient({
scheme: "frontend",
storagePrefix: "frontend",
storage: SecureStore,
}),
],
});- Never store passwords in plain text.
- Use a strong hashing algorithm.
The Better-Auth library handles this internally using the scrypt algorithm.
Important security features of the Better-Auth library is explained here. Using the Better-Auth library over custom security implementations is safer and more secure because the library is battle-tested and maintained by security experts, reducing the risk of common mistakes like weak hashing, insecure cookie handling, or insecure session handling. It provides a maintained, auditable solution that follows industry best practices.
As the project develops, we will include:
- Input sanitation and validation on custom routes (Better-Auth sanitizes and validates internal auth routes only)
- Rate-limiting (not included in Better-Auth library)
- Brute Force Protection (not included in Better-Auth library)
- Email verification
And we may explore:
- Multi-factor authentication (MFA) for user accounts