Authentication
The Tanqueo Backend API uses Supabase Auth for user authentication with JWT (JSON Web Token) based session management.
Authentication Flow
The authentication system follows a standard OAuth2-style flow:
Login
Endpoint
Request Body
interface LoginRequest {
email: string;
password: string;
}
Example Request
curl -X POST http://localhost:5000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "securePassword123"
}'
Response
interface LoginResponse {
user: Usuario;
access_token: string;
refresh_token: string;
expires_at: number;
expires_in: number;
}
interface Usuario {
id: string;
email: string;
nombre: string;
rol: string;
}
Example Response
{
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "[email protected]",
"nombre": "Juan Pérez",
"rol": "admin"
},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMWIyYzNkNC...",
"refresh_token": "v1.MRjrcL7qlZZRQWo_Xkk...",
"expires_at": 1709564400,
"expires_in": 3600
}
The access_token expires after 1 hour (3600 seconds). Use the refresh_token to obtain a new access token without requiring the user to log in again.
Implementation Details
The login controller performs a two-step verification process:
src/controllers/auth.controller.ts
async login(req: Request<{}, {}, LoginRequest>, res: Response): Promise<void> {
const { email, password } = req.body;
// Step 1: Authenticate with Supabase Auth
const { data: authData, error: authError } = await supabase.auth.signInWithPassword({
email,
password
});
if (authError) {
res.status(400).json({ error: authError.message });
return;
}
// Step 2: Fetch user details from usuarios table
const { data: userData, error: userError } = await supabase
.from('usuarios')
.select('*')
.eq('id', authData.user.id)
.single();
if (userError || !userData) {
res.status(404).json({ error: 'Usuario no encontrado en la base de datos' });
return;
}
// Return combined response
const response: LoginResponse = {
user: {
id: authData.user.id,
email: userData.email,
nombre: userData.nombre,
rol: userData.rol
},
access_token: authData.session.access_token,
refresh_token: authData.session.refresh_token,
expires_at: authData.session.expires_at || 0,
expires_in: authData.session.expires_in || 3600
};
res.json(response);
}
Using Access Tokens
All protected endpoints require the Authorization header with the Bearer token:
Authorization: Bearer <access_token>
Example Authenticated Request
curl http://localhost:5000/api/tanqueos \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Middleware Verification
The authMiddleware validates tokens on every protected route:
src/middleware/auth.middleware.ts
export async function authMiddleware(
req: AuthRequest,
res: Response,
next: NextFunction
): Promise<void> {
// Extract token from Authorization header
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
res.status(401).json({ error: 'Token no proporcionado' });
return;
}
// Verify token with Supabase
const { data: { user }, error } = await supabase.auth.getUser(token);
if (error || !user) {
res.status(401).json({ error: 'Token inválido' });
return;
}
// Fetch user data from database
const { data: userData, error: userError } = await supabase
.from('usuarios')
.select('*')
.eq('id', user.id)
.single();
if (userError || !userData) {
res.status(401).json({ error: 'Usuario no encontrado en la base de datos' });
return;
}
// Attach user to request
req.user = {
id: user.id,
email: userData.email,
nombre: userData.nombre,
rol: userData.rol
};
// Create authenticated Supabase client for RLS
req.accessToken = token;
req.supabase = createAuthClient(token);
next();
}
The middleware creates an authenticated Supabase client (req.supabase) with the user’s token. This ensures that Row Level Security (RLS) policies are automatically applied to all database queries.
Refresh Token Mechanism
When to Refresh
Access tokens expire after 1 hour. Before making requests with an expired token:
- Check if
expires_at timestamp has passed
- If expired, use the
refresh_token to get a new access_token
- Update your stored tokens
Refresh Endpoint
Request Body
{
"refresh_token": "v1.MRjrcL7qlZZRQWo_Xkk..."
}
Example Request
curl -X POST http://localhost:5000/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "v1.MRjrcL7qlZZRQWo_Xkk..."
}'
Response
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMWIyYzNkNC...",
"refresh_token": "v1.NewRefreshTokenHere...",
"expires_at": 1709568000,
"expires_in": 3600
}
Both the access_token and refresh_token are rotated on refresh for security. Always update both tokens in your client storage.
Implementation
src/controllers/refreshToken.controller.ts
export const refreshTokenController = {
async refresh(req: Request, res: Response): Promise<void> {
const { refresh_token } = req.body;
if (!refresh_token) {
res.status(400).json({ error: 'Refresh token es requerido' });
return;
}
// Refresh session with Supabase
const { data, error } = await supabase.auth.refreshSession({
refresh_token
});
if (error || !data.session) {
res.status(401).json({ error: 'Refresh token inválido o expirado' });
return;
}
const response = {
access_token: data.session.access_token,
refresh_token: data.session.refresh_token,
expires_at: data.session.expires_at || 0,
expires_in: data.session.expires_in || 3600
};
res.json(response);
}
};
Logout
Endpoint
This endpoint requires authentication (valid Bearer token).
Example Request
curl -X POST http://localhost:5000/api/auth/logout \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response
{
"message": "Sesión cerrada exitosamente"
}
Get Current User
Retrieve the currently authenticated user’s information:
Endpoint
Example Request
curl http://localhost:5000/api/auth/user \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response
{
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "[email protected]",
"nombre": "Juan Pérez",
"rol": "admin"
}
}
Row Level Security (RLS)
The authentication system integrates with Supabase’s Row Level Security:
// Function to create authenticated client with user token
export function createAuthClient(accessToken: string): SupabaseClient {
return createClient(supabaseUrl, supabaseKey, {
global: {
headers: {
Authorization: `Bearer ${accessToken}`
}
}
});
}
When using req.supabase in controllers, all database queries automatically:
- Apply RLS policies based on the authenticated user
- Filter data according to user permissions
- Enforce security rules at the database level
Error Responses
| Status Code | Error | Description |
|---|
| 400 | Email y contraseña son requeridos | Missing credentials |
| 400 | Invalid login credentials | Wrong email or password |
| 401 | Token no proporcionado | Missing Authorization header |
| 401 | Token inválido | Expired or invalid token |
| 401 | Refresh token inválido o expirado | Cannot refresh session |
| 404 | Usuario no encontrado en la base de datos | User exists in Auth but not in usuarios table |
| 500 | Error en el servidor | Internal server error |
Security Best Practices
Never expose access tokens or refresh tokens in:
- URL parameters
- Logs or error messages
- Client-side localStorage (use httpOnly cookies if possible)
- Git repositories or version control
Do:
- Store tokens securely (encrypted storage or secure cookies)
- Implement token refresh before expiration
- Use HTTPS in production
- Validate tokens on every protected request
- Implement proper CORS policies