Skip to main content

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

POST /api/auth/login

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

Authorization Header Format

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:
  1. Check if expires_at timestamp has passed
  2. If expired, use the refresh_token to get a new access_token
  3. Update your stored tokens

Refresh Endpoint

POST /api/auth/refresh

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

POST /api/auth/logout
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

GET /api/auth/user

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:
src/config/supabase.ts
// 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 CodeErrorDescription
400Email y contraseña son requeridosMissing credentials
400Invalid login credentialsWrong email or password
401Token no proporcionadoMissing Authorization header
401Token inválidoExpired or invalid token
401Refresh token inválido o expiradoCannot refresh session
404Usuario no encontrado en la base de datosUser exists in Auth but not in usuarios table
500Error en el servidorInternal 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

Build docs developers (and LLMs) love