Security & Authentication - FactoriaF5-Asturias/project-p5-digital-academy-team1-restaurant-frontend GitHub Wiki
Authentication
The backend issues a JWT stored in httpOnly cookies. The frontend never reads or stores the token: the auth store only keeps the user data and role returned by /auth/me.
sequenceDiagram
participant U as User
participant F as Frontend (Vue)
participant B as Backend (Spring Boot)
U->>F: Submits login form
F->>B: POST /api/v1/auth/login
B-->>F: 200 + Set-Cookie (httpOnly JWT) + XSRF-TOKEN cookie
F->>B: GET /api/v1/auth/me
B-->>F: User data + roles
F->>F: auth store saves user and role (no token)
U->>F: Navigates to a protected view
F->>F: Router guard checks role
F->>B: Request with cookies + X-XSRF-TOKEN header
B-->>F: 200 / 401 / 403
Why cookies instead of localStorage
- An
httpOnlycookie cannot be read by JavaScript, so an XSS vulnerability cannot steal the session. - The browser sends the cookies automatically on each request (
withCredentials: true). - Cookies are
SecureandSameSite=None, which is why both apps run over HTTPS in development.
XSRF protection
Because the session travels in cookies, the backend also requires an XSRF token. Axios copies the XSRF-TOKEN cookie into the X-XSRF-TOKEN header automatically:
// src/services/api.js
const api = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
withCredentials: true,
withXSRFToken: true,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
})
Automatic session renewal
A response interceptor in api.js handles expired tokens without the user noticing:
flowchart TD
A[Response with error] --> B{Status}
B -->|403| F[onForbidden: go to Acceso denegado]
B -->|other| R[Reject the error]
B -->|401| C{Request was login?}
C -->|Yes| R
C -->|No| D{Already refreshing?}
D -->|Yes| Q[Queue the request until the refresh ends]
D -->|No| E[GET /auth/refresh]
E -->|OK| G[Retry original + queued requests]
E -->|Fails| H[onUnauthorized: clear session, go to Login]
- Credentials errors on login are not retried.
- Each request is retried only once (
_retryflag) to avoid infinite loops. api.jsdoes not import the router or the stores: the app registers what to do on401/403withsetSessionHandlers(src/router/sessionRedirects.js), keeping the HTTP layer decoupled.
Authorization (Route Guards)
Each route declares its allowed roles in meta.roles, and a global beforeEach guard applies these rules (src/router/guards.js):
- Unknown URL → guests go to Login, logged-in users to the Carta.
- Not logged in and the route needs a role → Login.
- Logged in on a guest-only route (login, register) → Carta.
- Logged in with the wrong role → Acceso denegado.
Route guards improve the user experience, but real security is enforced by the backend: every protected endpoint validates the session and role on the server.
Data Stored in the Browser
| Data | Storage | Sensitive |
|---|---|---|
| Cart (products and quantities) | localStorage |
No |
| Device identifier (random UUID used for table detection) | localStorage |
No |
| Session (JWT) | httpOnly cookie |
Yes — not accessible from JS |
Form Validation
- Client-side validation gives immediate feedback (required fields, email format, postal code, product fields in the admin form —
utils/productValidation.js). - Actions stay disabled until the data is valid (e.g. "Confirm order" without payment method).
- Server-side validation errors are shown to the user without losing the data entered.