The Rodando Driver app implements a comprehensive authentication system that supports both web and mobile session types, with different token management strategies for each platform.
Authentication Flow
The authentication process follows a standard OAuth2-like flow with JWT tokens and refresh token rotation:
User Login
The driver enters credentials (email/password) through the login form.
Token Issuance
The backend validates credentials and returns:
accessToken - Short-lived JWT for API requests
refreshToken - Long-lived token (mobile only)
sessionType - Either 'web' or 'mobile'
accessTokenExpiresAt - Unix timestamp for token expiry
Token Storage
Tokens are stored based on session type:
Web : refreshToken stored in HttpOnly cookies
Mobile : Both tokens stored in localStorage
Authenticated Requests
The accessToken is attached to all API requests via the Authorization header.
Session Types
The app supports two distinct session types with different security models:
Mobile Session
Web Session
Mobile sessions store the refresh token locally and send it in request bodies. interface RefreshResponseMobile {
accessToken : string ;
refreshToken : string ; // New refresh token (rotation)
accessTokenExpiresAt : number ;
}
Characteristics:
Refresh tokens sent in request body
Token rotation on every refresh
Stored in localStorage
Longer token lifetime
Web sessions use HttpOnly cookies for enhanced security. interface RefreshResponseWeb {
accessToken : string ;
accessTokenExpiresAt : number ;
// refreshToken stored in HttpOnly cookie
}
Characteristics:
Refresh token in HttpOnly cookie
Protected from XSS attacks
Shorter token lifetime
Requires withCredentials: true
Login Implementation
The login process is handled by the AuthService with automatic response validation:
login ( payload : LoginPayload , httpOptions ?: { withCredentials? : boolean }): Observable < LoginResponse > {
const url = ` ${ this . baseUrl } /auth/login` ;
return this.http.post<ApiResponse<LoginResponse>>( url , payload , httpOptions).pipe(
map ( resp => this . unwrap < ApiResponse < LoginResponse >, LoginResponse >( resp , url )),
// Validate required fields
map ( res => {
if (! res ? .accessToken || ! res ? .sessionType) {
throw new Error ( 'Login response malformed: missing accessToken or sessionType' );
}
return res;
}),
catchError ( err => this . handleErrorAsApiError ( err , url ))
);
}
Login Payload:
interface LoginPayload {
email : string ;
password : string ;
audience ?: 'driver_app' | 'passenger_app' | 'admin_panel' ;
}
Token Refresh
The unified refresh() method handles both web and mobile token refresh automatically:
Web Refresh
Mobile Refresh
// Refresh using HttpOnly cookie
refresh ( refreshToken ?: string , useCookie = true ): Observable < RefreshResponse > {
const url = ` ${ this . baseUrl } /auth/refresh` ;
if ( useCookie ) {
// WEB/API_CLIENT (cookie HttpOnly)
return this . http . post < ApiResponse < RefreshResponseWeb >>(
url ,
{},
{ withCredentials: true }
). pipe (
map ( resp => this . unwrap < ApiResponse < RefreshResponseWeb >, RefreshResponseWeb >( resp , url )),
map ( res => this . validateRefreshWebResponse ( res , url )),
catchError ( err => this . handleErrorAsApiError ( err , url ))
);
}
}
The refresh method automatically validates that accessTokenExpiresAt is present and that the response contains all required fields before returning.
Logout
Logout is handled differently for each session type:
logoutWeb (): Observable < void > {
const url = ` ${ this . baseUrl } /auth/logout` ;
return this.http.post<void>( url , {}, { withCredentials : true }).pipe(
catchError ( err => this . handleErrorAsApiError ( err , url ))
);
}
Web logout clears the HttpOnly cookie on the server side. logoutMobile ( refreshToken : string ): Observable < void > {
const url = ` ${ this . baseUrl } /auth/logout` ;
return this.http.post<void>( url , { refreshToken }).pipe(
catchError ( err => this . handleErrorAsApiError ( err , url ))
);
}
Mobile logout requires sending the refresh token to invalidate it on the backend.
User Profile
Retrieve the authenticated driver’s profile information:
me ( useCookie : boolean = true ): Observable < UserProfile > {
const url = ` ${ this . baseUrl } /users/profile` ;
const options = useCookie ? { withCredentials: true } : {};
return this. http
.get<{ success : boolean ; message ?: string ; data ?: UserProfile }>( url , options)
.pipe(
map ( res => {
if (! res || typeof res.data !== 'object' || res.data === null ) {
throw new Error ( 'Profile response malformed' );
}
return res.data as UserProfile ;
}),
catchError ( err => this . handleErrorAsApiError ( err , url ))
);
}
UserProfile Interface:
interface UserProfile {
id : string | null ;
name : string | null ;
email : string | null ;
phoneNumber ?: string | null ;
profilePictureUrl ?: string | null ;
createdAt ?: string ;
}
Error Handling
The authentication service includes comprehensive error normalization:
private handleErrorAsApiError ( err : any , requestUrl ?: string ) {
return from ( normalizeAnyError ( err , requestUrl )). pipe (
mergeMap (( apiErr : ApiError ) => throwError (() => apiErr ))
);
}
ApiError Structure:
interface ApiError {
status ?: number ; // HTTP status code
message : string ; // Human-readable error message
code ?: string ; // Application error code
validation ?: any ; // Validation errors
raw ?: any ; // Raw error response
url ?: string | null ; // Request URL
}
Network Errors: When a network error occurs (status 0 or ProgressEvent), the error handler returns a user-friendly message: “Network error: please check your connection.”
Response Validation
All authentication responses are validated to ensure data integrity:
Web Response Validation
Mobile Response Validation
private validateRefreshWebResponse (
res : RefreshResponseWeb | null | undefined ,
requestUrl : string
): RefreshResponseWeb {
if ( ! res || typeof res . accessToken !== 'string' ) {
throw new Error ( `Respuesta de refresh (web) malformada: ${ requestUrl } ` );
}
if ( typeof ( res as any ). accessTokenExpiresAt !== 'number' ) {
throw new Error ( `Missing accessTokenExpiresAt in refresh (web): ${ requestUrl } ` );
}
return res ;
}
Security Best Practices
HttpOnly Cookies Web sessions use HttpOnly cookies to prevent XSS attacks from accessing refresh tokens.
Token Rotation Mobile sessions implement refresh token rotation - a new refresh token is issued with each refresh request.
Short-Lived Access Tokens Access tokens expire quickly (typically 15-30 minutes) to minimize the impact of token theft.
Error Normalization All errors are normalized to prevent sensitive information leakage in error messages.