Authentication
Overview
ForgeX uses SuperTokens with Google OAuth for authentication. Users cannot self-register - all accounts must be invited by an admin.
Authentication Flow
Admin creates invitation via /api/admin/users/invite. User receives email with invitation link.
User clicks invitation link and signs in with Google OAuth.
Backend validates Google token, updates user status to ACTIVE, and creates session.
User makes API requests with session cookie (HTTP-only).
Google OAuth Login
Step 1: Initiate Login
GET /api/auth/authorisationurl?thirdPartyId=google
Response:
{
"urlWithQueryParams": "https://accounts.google.com/o/oauth2/v2/auth?..."
}
Step 2: Google Callback
After user authenticates with Google:
GET /api/auth/callback/google?code=AUTHORIZATION_CODE
Success Response:
- Sets HTTP-only session cookies
- Redirects to dashboard
Error Responses:
- No Account Found
- Pending Invitation
- Account Disabled
{
"error": "No account found. Contact admin for invitation."
}
{
"error": "Accept invitation first."
}
{
"error": "Account disabled."
}
Session Management
ForgeX uses SuperTokens for session management with HTTP-only cookies. This provides enterprise-grade security without the complexity of JWT token management.
How SuperTokens Sessions Work
- Session Creation
- Session Validation
- Cross-Subdomain SSO
- Token Refresh
When a user logs in via Google OAuth:
- Google validates the user's identity
- Backend verifies the user exists and is ACTIVE
- SuperTokens creates a new session with:
- Access token (short-lived, ~1 hour)
- Refresh token (long-lived, ~100 days)
- Anti-CSRF token (for write operations)
- Cookies are set with these tokens (HTTP-only, secure)
The session contains:
{
userId: "user-uuid",
userDataInJWT: {
email: "user@example.com",
role: "ESTIMATOR",
name: "John Smith"
}
}
On every API request:
- Browser automatically sends session cookies
- SuperTokens SDK validates the access token
- If access token expired:
- SuperTokens uses refresh token to create new access token
- New cookies sent in response
- Request proceeds seamlessly (no user interruption)
- If refresh token expired:
- User is logged out (redirect to login)
No manual token management required - it's all automatic.
SuperTokens session cookies work across all ForgeX subdomains:
// Cookie configuration
{
domain: '.precisionsiteservices.com', // All subdomains
httpOnly: true, // Not accessible via JS
secure: true, // HTTPS only
sameSite: 'lax' // CSRF protection
}
Result: Sign in once at forge.*, access bids.*, projects.*, field.* without re-authenticating.
Access tokens refresh automatically:
- Access token lifetime: 1 hour
- Refresh token lifetime: 100 days
- Auto-refresh: When access token expires, SuperTokens SDK automatically uses refresh token to get new access token
- User experience: Seamless - no logout, no interruption
Session truly expires only when:
- User explicitly logs out
- Refresh token expires (100 days inactive)
- Admin revokes session
Session Cookies
Sessions are stored in HTTP-only cookies with the following configuration:
{
httpOnly: true, // Not accessible via JavaScript (XSS protection)
secure: true, // HTTPS only (production)
sameSite: 'lax', // CSRF protection
domain: '.precisionsiteservices.com' // All subdomains share auth
}
Cookies set by SuperTokens:
sAccessToken- Short-lived access token (~1 hour)sRefreshToken- Long-lived refresh token (~100 days)sFrontToken- Frontend metadata (user info, not sensitive)sIdRefreshToken- Anti-CSRF token (for state changes)
No JWT in localStorage: SuperTokens uses HTTP-only cookies exclusively. This prevents XSS attacks - malicious JavaScript cannot steal session tokens.
Session Validation
All protected endpoints automatically validate session cookies. No manual token handling required.
# Session cookie automatically included
GET /api/bids
Cookie: sAccessToken=...; sRefreshToken=...
Logout
POST /api/auth/signout
Clears session cookies and invalidates session.
Invitation System
Admin Invites User
- Request
- Response
POST /api/admin/users/invite
Content-Type: application/json
Cookie: sAccessToken=...; sRefreshToken=...
{
"email": "user@example.com",
"name": "John Smith",
"role": "ESTIMATOR",
"services": [
{ "service": "BIDS", "role": null },
{ "service": "PROJECTS", "role": "PM" }
]
}
{
"user": {
"id": "uuid",
"email": "user@example.com",
"status": "PENDING_INVITATION",
...
},
"invitationUrl": "https://portal.example.com/invite?token=abc123...",
"expiresAt": "2026-01-23T10:00:00.000Z"
}
An invitation email is automatically sent via Gmail API. If delivery fails, the invitation URL is still returned for manual sharing.
Get Invitation Info
GET /api/auth/invitation/:token
Response:
{
"email": "user@example.com",
"name": "John Smith",
"role": "ESTIMATOR",
"services": ["BIDS", "PROJECTS"],
"invitedBy": {
"name": "Admin User",
"email": "admin@example.com"
},
"expiresAt": "2026-01-20T10:00:00.000Z",
"isExpired": false
}
Accept Invitation
POST /api/auth/accept-invitation
Content-Type: application/json
{
"token": "abc123...",
"googleToken": "google-oauth-token"
}
Response:
{
"user": {
"id": "uuid",
"email": "user@example.com",
"name": "John Smith",
"role": "ESTIMATOR"
}
}
Session cookies (sAccessToken, sRefreshToken) are set automatically in the response headers.
User Status States
| Status | Can Login | Description |
|---|---|---|
PENDING_INVITATION | ❌ | Invited but hasn't accepted |
ACTIVE | ✅ | Can log in normally |
DISABLED | ❌ | Account disabled by admin |
Environment Variables
Backend (Required)
SUPERTOKENS_CONNECTION_URI=https://supertokens.example.com
API_DOMAIN=https://api.precisionsiteservices.com
WEBSITE_DOMAIN=https://portal.precisionsiteservices.com
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
Frontend (Required)
VITE_API_URL=https://api.precisionsiteservices.com
VITE_GOOGLE_CLIENT_ID=your-client-id
Security Features
HTTP-Only Cookies
Session tokens not accessible via JavaScript (XSS protection)
Secure Cookies
HTTPS only in production
SameSite Protection
Prevents CSRF attacks
Invitation-Based
No self-registration - admin must invite
Troubleshooting
Session expired error
Sessions expire after inactivity. User must log in again via Google OAuth.
Wrong Google account
User must sign in with the same Google account that received the invitation.
Invitation expired
Admin must resend invitation via /api/admin/users/:id/resend-invite.
No account found
User must receive invitation from admin before logging in.
Related Resources
- User Management Endpoints
- Role Permissions
- Architecture Diagram