================================================================================ MIGRACIÓN REACT NATIVE: AUTENTICACIÓN CON JWT (backend Fercogan) ================================================================================ Documento para adaptar el frontend (React Native) a los nuevos endpoints y al uso del token Bearer. Base URL de ejemplo: https://testapp.digitaltelecom.net -------------------------------------------------------------------------------- 1. RESUMEN DEL CAMBIO -------------------------------------------------------------------------------- - Antes: el "login" usaba GET /api/usuarios/confirmado/{email} sin contraseña y guardaba solo email en AsyncStorage (inseguro). - Ahora: el login real es POST /api/usuarios/login con email (username) y contraseña. El servidor devuelve un JWT que debe enviarse en las peticiones protegidas: header Authorization: Bearer . - La mayoría de rutas /api/* (remates, lotes, pujas, etc.) requieren JWT salvo las excepciones listadas abajo. -------------------------------------------------------------------------------- 2. ENDPOINTS PÚBLICOS (sin Authorization) -------------------------------------------------------------------------------- POST /api/usuarios/login → login (body JSON) POST /api/usuarios/registrar → registro GET /api/usuarios/confirmado/{username} → solo verificación registro/aprobado (útil en flujo signup; no sustituye login) GET /auth/verificar/{username} → mismo concepto que confirmado (signup) GET /auth/existe/{username} → según implementación actual OPTIONS /** → CORS preflight /ws/** , /contador/** → según necesidad (actualmente públicos) -------------------------------------------------------------------------------- 3. LOGIN (SIGN IN) — REEMPLAZA confirmado + AsyncStorage sin token -------------------------------------------------------------------------------- Método: POST URL: {BASE_URL}/api/usuarios/login Headers: Content-Type: application/json Body (JSON): { "username": "", "password": "" } Respuesta 200 (JSON) — guardar estos valores en AsyncStorage: { "token": "", "tokenType": "Bearer", "expiresInMs": 86400000, "username": "...", "userId": 1, "rolId": 1, "authenticated": true, "confirmed": true } Errores: - 401 + { "error": "Credenciales inválidas" } → usuario inexistente o contraseña incorrecta - 403 + { "error": "Usuario pendiente de aprobación" } → existe pero aún no aprobado Acciones en el cliente tras 200: 1) AsyncStorage.setItem("authToken", data.token); 2) AsyncStorage.setItem("usuario", data.username); 3) AsyncStorage.setItem("rol", String(data.rolId)); // ya no hace falta GET /rol/{email} 4) AsyncStorage.setItem("isLoggedIn", "true"); 5) Opcional: guardar userId para depuración. NO guardar la contraseña en AsyncStorage. -------------------------------------------------------------------------------- 4. SESIÓN ACTUAL / USUARIO LOGUEADO -------------------------------------------------------------------------------- GET {BASE_URL}/api/usuarios/me Headers: Authorization: Bearer Accept: application/json Respuesta 200 ejemplo: { "username": "...", "userId": 1, "rolId": 1, "confirmed": true } Usar al abrir la app para validar que el token sigue siendo válido; si 401, limpiar AsyncStorage y volver a pantalla de login. -------------------------------------------------------------------------------- 5. PETICIONES PROTEGIDAS (remates, pujas, lotes, cabañas, etc.) -------------------------------------------------------------------------------- En TODAS las llamadas fetch/axios a /api/... (excepto login, registrar, confirmado y auth/verificar según corresponda), añadir: headers: { "Content-Type": "application/json", "Authorization": "Bearer " + (await AsyncStorage.getItem("authToken")) } Si el servidor responde 401, el token expiró o es inválido: cerrar sesión en cliente (borrar authToken, isLoggedIn) y redirigir a login. -------------------------------------------------------------------------------- 6. SIGN UP (REGISTRO) — CAMBIOS MÍNIMOS -------------------------------------------------------------------------------- - GET /auth/verificar/{email} puede seguir usándose sin token para decidir si el usuario ya existe o está aprobado (flujo actual). - POST /api/usuarios/registrar sigue siendo público; body igual que antes (username=email, password, celular, ci, nombre, rol). Tras registro exitoso, el usuario sigue pendiente de aprobación: NO recibirá JWT válido para app hasta que un admin apruebe; el login con POST /login devolverá 403 mientras tanto. -------------------------------------------------------------------------------- 7. EJEMPLO handleSignIn (lógica sugerida, pseudocódigo) -------------------------------------------------------------------------------- async function handleSignIn() { setLoading(true); setError(""); if (!validateEmail(email)) { setError("Email inválido"); setLoading(false); return; } const res = await fetch(BASE + "/api/usuarios/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: email, password: password }), }); const data = await res.json().catch(() => ({})); if (res.status === 401) { setError(data.error || "Credenciales inválidas"); setLoading(false); return; } if (res.status === 403) { setError(data.error || "Cuenta pendiente de aprobación"); setLoading(false); return; } if (!res.ok) { setError("Error de servidor"); setLoading(false); return; } await AsyncStorage.setItem("authToken", data.token); await AsyncStorage.setItem("usuario", data.username); await AsyncStorage.setItem("rol", String(data.rolId)); await AsyncStorage.setItem("isLoggedIn", "true"); navigation.replace("RematesList"); setLoading(false); } Eliminar: GET confirmado/{email} como sustituto del login, y la llamada separada a GET /api/usuarios/rol/{email} tras login (los datos vienen en la respuesta del login). -------------------------------------------------------------------------------- 8. VARIABLES DE ENTORNO BACKEND (operaciones) -------------------------------------------------------------------------------- En el servidor, definir JWT_SECRET (cadena larga y aleatoria, mínimo 32 bytes en UTF-8) y opcionalmente JWT_EXPIRATION_MS. El valor por defecto en application.properties es solo para desarrollo. ================================================================================ FIN DEL DOCUMENTO ================================================================================