PitchPro uses a dual-token JWT strategy to balance security and usability. A short-lived access token is sent with every API request, while a longer-lived refresh token — stored server-side in theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/JuanSerna14/Final-lenguaje-Avanzado/llms.txt
Use this file to discover all available pages before exploring further.
users table — allows the client to obtain a new access token without requiring the user to log in again. All authentication routes live under /api/auth and are handled by src/modules/auth/auth.controller.ts.
Token Strategy
| Access Token | Refresh Token | |
|---|---|---|
| Signed with | JWT_SECRET (env var, default secret_key_123) | JWT_REFRESH_SECRET (env var, default refresh_secret_key_456) |
| Expiry | 15 minutes | 7 days |
| Payload | { id, email } | { id } |
| Stored in DB? | No | Yes — in users.refresh_token column |
| Used for | Authorizing protected API requests | Issuing a new access token |
Registration Flow
The controller queries
SELECT * FROM users WHERE email = $1. If a row is found, it returns 400 with { errors: [{ msg: "El email ya está registrado" }] }.bcryptjs generates a salt with 10 rounds and hashes the password before it is ever written to the database.Login Flow
The
validateLogin middleware checks that email is a valid email format and password is not empty. Validation failures return 400.If no user is found, returns
400 with { errors: [{ msg: "Credenciales inválidas" }] } — the same generic message used for a wrong password, to prevent user enumeration.bcrypt.compare rehashes the submitted password with the stored salt and compares it to the stored hash. If it does not match, returns 400.const accessToken = jwt.sign(
{ id: user.id, email: user.email },
JWT_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ id: user.id },
JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
Storing the refresh token server-side means it can be revoked at any time (e.g., on logout or suspicious activity).
Token Refresh
When the access token expires, the client sends the refresh token to obtain a new one — without requiring the user to re-enter credentials. Endpoint:POST /api/auth/refresh
- The controller verifies the JWT signature using
JWT_REFRESH_SECRET. - It then queries the database:
SELECT * FROM users WHERE id = $1 AND refresh_token = $2— the token must be both cryptographically valid and match what is stored in the database. - If both checks pass, a new access token is signed (
expiresIn: '15m') and returned.
401 or 403 respectively.
Logout
Endpoint:POST /api/auth/logout
verifyToken Middleware
All routes under /api/canchas and /api/reservas are protected by the verifyToken middleware defined in src/middlewares/verifyToken.ts:
src/middlewares/verifyToken.ts
- Reads the
Authorizationheader and checks it starts withBearer. - Extracts the token string after the space.
- Calls
jwt.verify(token, JWT_SECRET)— this validates the signature and expiry in one step. - On success, attaches the decoded payload (
{ id, email, iat, exp }) toreq.userso route handlers can access the authenticated user’s identity. - On failure (missing header, invalid signature, or expired token), returns
401immediately and does not callnext().
src/main.ts:
/api/auth routes are explicitly not guarded by verifyToken — registration, login, and refresh must be accessible without an existing token.
Environment Variables
| Variable | Default (dev only) | Purpose |
|---|---|---|
JWT_SECRET | secret_key_123 | Signs and verifies access tokens |
JWT_REFRESH_SECRET | refresh_secret_key_456 | Signs and verifies refresh tokens |
Auth API Reference
Full request/response specs for every /api/auth endpoint.
Frontend Auth Flow
How the frontend stores tokens and handles expiry.