This document describes the authentication system used in the Flight Cost Monitor application.
FCM supports two main authentication methods:
- OAuth authentication (GitHub, Google)
- Credentials-based authentication
sequenceDiagram
participant U as User
participant W as FCM WebUI
participant O as OAuth Provider
participant A as FCM API
participant D as Database
%% OAuth Flow
U->>W: Click OAuth Login
W->>O: Initiate OAuth Flow
O->>U: Show Provider Login
U->>O: Enter Credentials
O->>W: Return OAuth Token + User Info
%% Backend Authentication
W->>A: POST /auth/oauth/login
A->>D: Find/Create User
A->>A: Generate JWT & Refresh Token
A->>W: Return Tokens + User Info
W->>U: Redirect to Dashboard
sequenceDiagram
participant U as User
participant W as FCM WebUI
participant A as FCM API
participant D as Database
%% Login Flow
U->>W: Enter Credentials
W->>A: POST /auth/login
A->>D: Validate Credentials
A->>A: Generate JWT & Refresh Token
A->>W: Return Tokens + User Info
W->>U: Redirect to Dashboard
stateDiagram-v2
[*] --> ValidToken
ValidToken --> ExpiredToken: Token Expires
ExpiredToken --> RefreshAttempt: Auto Refresh
RefreshAttempt --> ValidToken: Success
RefreshAttempt --> LoginRequired: Failure
LoginRequired --> [*]: Redirect to Login
note right of ValidToken
JWT in memory
Used for API calls
end note
note right of RefreshAttempt
Uses httpOnly
refresh token
end note
interface TokenPayload {
sub: string; // User ID
email: string;
authType: string; // 'CREDENTIALS' | 'OAUTH'
roles: string[];
iat: number;
exp: number;
}
interface RefreshToken {
id: string;
token: string;
userId: string;
expiresAt: Date;
family: string; // For token rotation
generationNumber: number;
}
POST /auth/oauth/login
: Exchange OAuth token for JWTPOST /auth/oauth/github
: GitHub OAuth callbackPOST /auth/oauth/google
: Google OAuth callback
POST /auth/login
: Login with username/passwordPOST /auth/register
: Register new userPOST /auth/forgot-password
: Request password resetPOST /auth/reset-password
: Reset password with token
POST /auth/refresh
: Refresh access tokenPOST /auth/logout
: Logout and invalidate tokens
graph TD
A[Access Token] -->|Stored in| B[Memory]
C[Refresh Token] -->|Stored in| D[httpOnly Cookie]
E[OAuth Token] -->|Temporary| F[Memory during OAuth Flow]
-
Token Rotation
- New refresh token family on login
- Generation number increments on refresh
- Family invalidation on suspected breach
-
CSRF Protection
- SameSite cookie policy
- CSRF tokens for mutations
- Origin validation
-
Rate Limiting
- Login attempts limited
- Token refresh rate limited
- IP-based throttling
// OAuth Login
const handleOAuthLogin = async (provider: 'github' | 'google') => {
const tokens = await authService.loginWithOAuth(provider);
await tokenStorage.setTokens(tokens);
};
// Credentials Login
const handleLogin = async (credentials: LoginCredentials) => {
const tokens = await authService.login(credentials);
await tokenStorage.setTokens(tokens);
};
// API Call with Auth
const makeAuthenticatedCall = async () => {
const token = await tokenStorage.getAccessToken();
return api.call('/protected-endpoint', {
headers: { Authorization: `Bearer ${token}` }
});
};
// Token Validation
@Injectable()
export class AuthGuard implements CanActivate {
async canActivate(context: ExecutionContext) {
const token = this.extractToken(context);
if (!token) return false;
try {
const payload = await this.jwtService.verify(token);
return this.validatePayload(payload);
} catch {
return false;
}
}
}
// Role-Based Access
@Injectable()
export class RolesGuard implements CanActivate {
async canActivate(context: ExecutionContext) {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
const user = context.switchToHttp().getRequest().user;
return this.matchRoles(roles, user.roles);
}
}
graph TD
A[Authentication Error] -->|Invalid Credentials| B[401 Unauthorized]
A -->|Invalid Token| C[401 Unauthorized]
A -->|Expired Token| D[401 Unauthorized + Refresh Required]
A -->|Invalid Refresh| E[401 Unauthorized + Login Required]
A -->|Missing Permissions| F[403 Forbidden]
- Access tokens expire in 15 minutes
- Refresh tokens expire in 7 days
- Tokens are invalidated on logout
- Compromised tokens can be revoked
- Always use HTTPS
- Implement proper CORS policies
- Validate all user input
- Monitor failed login attempts
- Log security-relevant events
.env.local
configuration:
AUTH_GITHUB_ID=Ov23li0AghVx0GGNzgo7
AUTH_GITHUB_SECRET=6a101af4f6eb6a6199b850fe23689b8049c48acb
NEXT_PUBLIC_OAUTH_GOOGLE_CLIENT_ID=your_google_client_id
AUTH_SECRET=your-secret-key-min-32-chars-long
.env
configuration:
JWT_SECRET=your-jwt-secret-key-here
JWT_ACCESS_EXPIRATION=15m
JWT_REFRESH_EXPIRATION=7d