Frontend Architecture
This document describes the frontend architecture of the zero-trust control plane: Next.js App Router structure, auth context, gRPC/HTTP bridge, and session handling. For dashboard pages and browser flows, see Dashboard and User Browser.
Audience: Frontend developers onboarding or extending the web client.
Overview
The frontend is a Next.js application (App Router) that talks to the backend only via Next.js API routes. The browser never calls gRPC directly; API routes run on the server and use gRPC clients to call the backend, then return JSON (or redirects). Auth state (tokens, user/org ids) is stored in localStorage and exposed through an auth context; a 401 from any API triggers clear auth and redirect to login.
App structure
- Routes: app/ — App Router. Public:
/,/login,/register. Authenticated:/dashboard(and children),/browser. API routes underapp/api/. - API routes:
- auth:
login,logout,refresh,register,verify(credential verification for create-org flow; calls backend VerifyCredentials),mfa/request-with-phone,mfa/verify— AuthService, token issuance, MFA flow. - organization:
create— OrganizationService.CreateOrganization for creating new organizations after registration. - org-admin:
members,audit,policy-config,sessions— Org admin dashboard backend (Membership, Audit, OrgPolicyConfig, Session). - browser:
check-url,policy— CheckUrlAccess, GetBrowserPolicy for the user browser flow. - users:
by-email— UserService.GetUserByEmail (e.g. add member by email). - dev:
mfa/otp— Dev OTP for development.
- auth:
Auth context
- Provider: contexts/auth-context.tsx. Wraps the app and exposes
useAuth(). - Exposed:
user(user_id, org_id),accessToken,isAuthenticated,isLoading,login,verifyMFA,logout,refresh,setAuthFromResponse,clearAuth, handleSessionInvalid. - Storage: Tokens and user/org ids in localStorage; keys prefixed with
ztcp_. - 401 handling: When any API returns 401, the caller should invoke handleSessionInvalid() — it clears auth state and redirects to
/login. Used by dashboard and other protected pages. Logout (user-initiated) clears auth and redirects to/(home); handleSessionInvalid (session revoked or 401) redirects to/login.
gRPC/HTTP bridge
The browser does not call gRPC. Flow:
- Page or client code calls the API with the Bearer token in the
Authorizationheader (e.g.fetch('/api/...', { headers: { Authorization: 'Bearer ' + accessToken } })). - The API route uses getAccessToken(request) to read the Bearer token; if missing, returns 401.
- The route instantiates gRPC clients from lib/grpc/: auth-client.ts, organization-client.ts, dev-client.ts, org-admin-clients.ts. These connect to the backend gRPC server (URL from env, e.g.
BACKEND_GRPC_URLor similar). - Errors from gRPC are mapped to HTTP status and JSON with grpc-to-http.ts (
grpcErrorToHttp): e.g. UNAUTHENTICATED → 401, PERMISSION_DENIED → 403, NOT_FOUND → 404.
Session handling
- Refresh: The auth context exposes
refresh(); pages or middleware can call it to refresh the access token before expiry. API routes that need a valid token usegetAccessToken(request)and return 401 if absent. - Proactive refresh: Client code can refresh before long operations or on a timer; after 401, handleSessionInvalid() clears storage and redirects to login so the user re-authenticates.
User Registration and Organization Setup Flow
The frontend supports sign-in, registration, and organization creation as follows:
Home (/)
Sign in and Register links only; no create-organization on the home page. Create-organization is on the login page.
Login page (/login)
Two modes via shadcn/ui Tabs:
- Existing: Sign in with email, password, and organization ID. For users who are already members of an org (e.g. added via
MembershipService.AddMemberor who created an org earlier). - Create new: Create an organization and then log in. User enters email, password, and organization name. The frontend:
- Calls
POST /api/auth/verifywith email and password → BFF calls backendAuthService.VerifyCredentials→ returnsuser_id. - Calls
POST /api/organization/createwithuser_idandname→ backend creates org and owner membership. - Logs the user in with the same credentials and the new org id (redirect to dashboard).
- Calls
Both newly registered and already-registered users can create an org from the "Create new" tab (VerifyCredentials works for any valid email/password).
Register page (/register)
Registration only: email, password, optional name. On success, the user receives user_id. They can then go to /login and use the Create new tab to create an organization (VerifyCredentials + CreateOrganization), or use the Existing tab to sign in to an org they were added to.
API Route: Organization Creation
The user_id passed to this route is obtained from Register (after signup) or from VerifyCredentials (when creating from the login page "Create new" tab).
Route: POST /api/organization/create
Request Body:
{
name: string; // Organization name (required, min length 1)
user_id: string; // User ID from Register or VerifyCredentials (required, min length 1)
}
Response (success):
{
organization: {
id: string; // Generated organization ID (UUID)
name: string; // Organization name
status: string; // "ACTIVE" (auto-activated for PoC)
created_at: string; // ISO 8601 timestamp
}
}
Error Responses:
400 Bad Request: Missing or invalidnameoruser_id404 Not Found: User with provideduser_iddoes not exist500 Internal Server Error: Database error during creation
Implementation: See app/api/organization/create/route.ts and lib/grpc/organization-client.ts.