Inici de sessió: diferència entre les revisions
Es crea la pàgina amb «== Inici de sessió == == Connexió a la BD i inici de sessió (PHP) == == Formulari d'inici de sessió == Es mostra A més, en cas que no s’hagi registrat, tindrà un botó per registrar-se. A part, si no recorda de les seves credencials, tindrà un botó per poder recuperar la contrasenya. Per últim, també se li dona a l’usuari la oportunitat (sense obligar-lo) a autenticar-se amb el 2FA. == Inici de sessió manual == === Obtenció de les dades d'usua...». |
Cap resum de modificació |
||
| (Hi ha 4 revisions intermèdies que no es mostren del mateix usuari) | |||
| Línia 1: | Línia 1: | ||
== Inici de sessió == | == Inici de sessió == | ||
Quan els usuaris desitjen iniciar sessió, tenen un formulari on poden introduir el seu usuari i contrasenya. A més, tenen l'opció de que l'aplicatiu els recordi desant un token d'autenticació únic dins d'una galeta, si així ho desitja. | |||
== Connexió a la BD i inici de sessió (PHP) == | == Connexió a la BD i inici de sessió (PHP) == | ||
Sempre que es treballa amb sessions, el primer que hem de fer és iniciar-la abans d'escriure el codi HTML | |||
session_start(); | |||
A més, hem de requerir el fitxer amb les funcions i una connexió a la BD. En aquest cas, si realitzem una autenticació errònia, haurem d'incrementar el número d'intents de login de la BD i per tant requerim la [[Connexions a la BD (A5.3)#Connexió d'escriptura|connexió d'escriptura]]. | |||
require 'funcions.php'; | |||
require './connexioBD/connexioRW.php'; | |||
== Comprovar si està autenticat == | |||
En cas que l'usuari està autenticat, el redirigirem directament a la seva pàgina privada. Per comprovar si l'usuari està autenticat farem servir la funció [[Funcions de la pràctica 5.3#esta autenticat|esta_autenticat]].<pre> | |||
// Redirigir si ja hi ha una sessió activa | |||
if (esta_autenticat()) { | |||
header('Location: privada.php'); | |||
exit; | |||
} | |||
</pre> | |||
== Formulari d'inici de sessió == | == Formulari d'inici de sessió == | ||
Al formulari es mostra dues entrades de text per introduir el nom d'usuari i la contrasenya. A més, en cas que no s’hagi registrat, tindrà un botó per registrar-se, i si no recorda de les seves credencials, tindrà un botó per poder recuperar la contrasenya. | |||
Per últim, també se li dona a l’usuari la oportunitat (sense obligar-lo) a autenticar-se amb | |||
També es mostra una casella de selecció perque l'aplicatiu el "recordi" | |||
Per últim, també se li dona a l’usuari la oportunitat (sense obligar-lo) a autenticar-se amb 2FA. | |||
El formulari es visualitzaria de la següent forma: | |||
[[Fitxer:FormulariIniciSessio.png|center|miniatura|559x559px]] | |||
== Inici de sessió manual == | == Inici de sessió manual == | ||
=== Obtenció de les dades d'usuari === | === Obtenció de les dades d'usuari del formulari i la BD === | ||
Un cop es respongui el formulari, obtindrem les dades del formulari i realitzarem una consulta per comprovar si el nom d'usuari realment existeix<pre> | |||
// Recuperem les dades del formulari | |||
$nom_usuari = trim($_POST['nom_usuari']); | |||
$contrasenya = $_POST['contrasenya']; | |||
$recordar = isset($_POST['recordar']); // Opció per crear o no galetes | |||
// Recuperem les dades de l'usuari amb el nom d'usuari especificat | |||
$stmt = $pdo->prepare("SELECT * FROM usuaris WHERE nom_usuari = ?"); | |||
$stmt->execute([$nom_usuari]); | |||
$usuari = $stmt->fetch(); | |||
// Verifiquem que l'usuari existeixi | |||
if ($usuari) { | |||
</pre> | |||
=== Comprovació d'usuari bloquejat === | === Comprovació d'usuari bloquejat === | ||
En cas que existeixi, primerament comprovarem si el compte està bloquejat. Per això comprovarem: | |||
* Que el nombre d’intents d’inici de sessió que s’ha enregistrat a la BD sigui major o igual a $max_intents, en aquest cas 3 intents | |||
* Si l’últim intent d’inici de sessió s’ha produit fa menys de $bloqueig_minuts, en aquest cas 2 minuts | |||
Si es cumpleixen els 2 requisits, el compte estarà bloquejat.<pre> | |||
// Comprovar si hi ha bloqueig | |||
if ($usuari['intents_login'] >= $max_intents | |||
&& strtotime($usuari['ultim_intent']) > strtotime("-$bloqueig_minuts minutes")) { | |||
</pre>Per fer el càlcul de les dates fem servir la funció interna de PHP strtotime(), la qual retorna la data introduida en temps UNIX (nº de segons des de 1/1/1970). | |||
strtotime($usuari['ultim_intent']) retorna el número de segons que han passat del 1/1/1970 a la data especificada (o dit d'una altra manera, quans segons han passat des de l’1/1/1970 fins el 10/1/2026 a les 15:00) | |||
strtotime("-$bloqueig_minuts minutes") retorna el número de segons que han passat des de l’1/1/1970 fa $bloqueig_minuts minuts (o dit d'una altra manera, quans segons han passat des de l’1/1/1970 fa 2 minuts) | |||
Així, tenim la mateixa “base temporal” amb la que fer càlculs | |||
En cas que el compte estigui bloquejat, mostrarem a l’usuari el temps que queda per desbloquejar-lo. | |||
Per això: | |||
# Calculem el temps (l’hora) UNIX de l’últim intent | |||
## Exemple (Exemple: 15:00 emmagatzemat en format UNIX) | |||
# Calculem el temps (l’hora) UNIX per que s’acabi el bloqueig | |||
## Agafem el temps de l’últim intent i li sumem X minuts en segons, en aquest cas 2 minuts per 60 segons | |||
## Exemple: (Exemple: 15:02, emmagatzemat en format UNIX) | |||
# Calculem els segons restants | |||
## Agafem el temps (hora) perquè s’acabi el bloqueig i restem a l’hora actual | |||
## Exemple: 15:02 - 15:01 = 00:01 (60 segons) | |||
# Convertim els segons restants a minuts i segons | |||
## 80 segons = 1 minut i 20 segons | |||
# Mostrem els minuts i segons restants | |||
<pre> | |||
// Calcular temps restant | |||
$ultim_intent_ts = strtotime($usuari['ultim_intent']); //Calculem el temps (l’hora) UNIX de l’últim intent | |||
$final_bloqueig_ts = $ultim_intent_ts + ($bloqueig_minuts * 60); //Calculem el temps (l’hora) UNIX per que s’acabi el bloqueig | |||
$segons_restants = $final_bloqueig_ts - time(); // Calculem els segons restants. Agafem el temps (hora) perquè s’acabi el bloqueig i restem a l’hora actual | |||
$minuts_restants = floor($segons_restants / 60); // Convertim els segons restants a minuts | |||
$segons_restants = $segons_restants % 60; // Recuperem els segons restants després de calcular les hores | |||
$error = "Compte bloquejat temporalment. Torna-ho a provar en {$minuts_restants} minuts i {$segons_restants} segons."; | |||
</pre>Per tant el fluxe del codi seria el següent: | |||
# L’usuari s’equivoca 3 vegades | |||
# Intenta accedir una 4ta, no pot (3 intents_login, 3 segons últim intent) | |||
# Espera 2 minuts (3 intents_login, 2 minuts últim intent, pot accedir) | |||
# Es torna a equivocar una 5na vegada (3 intents_login, 3 segons últim intent) | |||
# Torna a esperar 2 minuts | |||
# Accedeix correctament | |||
=== Comprovació de contrasenya === | === Comprovació de contrasenya === | ||
Un cop verificat que l'usuari no està bloquejat, comprovarem que la contrasenya sigui correcta amb la funció [[Funcions de la pràctica 5.3#verificar contrasenya|verificar_contrasenya]].<pre> | |||
if (verificar_contrasenya($contrasenya, $usuari['contrasenya'])) { | |||
</pre>En cas que la contrasenya sigui errònia, augmentarem en 1 els número d'intents d'autenticació, registrarem una acció a la taula activitat de nom "error-login" i registrarem un missatge d'error<pre> | |||
// Si la contrasenya és incorrecte, sumem un intent de login | |||
$stmt = $pdo->prepare("UPDATE usuaris SET intents_login = intents_login + 1, ultim_intent = NOW() WHERE id = ?"); | |||
$stmt->execute([$usuari['id']]); | |||
// Registrar intent fallit | |||
registrar_activitat($pdo, $usuari['id'], 'error-login'); | |||
$error = "Usuari o contrasenya incorrecte"; | |||
</pre> | |||
=== Inici de sessió === | === Inici de sessió === | ||
Si la contrasenya és correcte, actualitzarem el nombre d’intents de login a 0<pre> | |||
// Login correcte, resetajer intents | |||
$stmt = $pdo->prepare("UPDATE usuaris SET intents_login = 0, ultim_intent = NULL WHERE id = ?"); | |||
$stmt->execute([$usuari['id']]); | |||
</pre>Seguidament, regenerarem la ID de sessió per evitar atacs de sessió fixada<pre> | |||
session_regenerate_id(true); // Protecció session fixation | |||
</pre>Per acabar creant un vector de nom “usuari” dins de la sessió amb els següents vectors: | |||
* ID de l’usuari dins la BD | |||
* Nom d’usuari | |||
* Nom complet | |||
* Rol, per definir si és administrador o no i quin tipus | |||
* Autenticat, per definir que està autenticat | |||
<pre> | |||
$_SESSION['usuari'] = [ // Iniciem sessió a l'usuari indicant | |||
'id' => $usuari['id'], // El seu ID a la BD | |||
'nom_usuari' => $usuari['nom_usuari'], // El nom d'usuari | |||
'nom_complet' => $usuari['nom_complet'], // El nom complet | |||
'rol' => $usuari['rol'], // El seu rol (usuari, admin) | |||
'autenticat' => true // Definim que està autenticat | |||
]; | |||
</pre>Un cop autenticat, registrarem una acció a la taula activitat de nom "login" i redirigirem l'usuari a la seva pàgina privada<pre> | |||
// Registrem l'acció "login" | |||
registrar_activitat($pdo, $usuari['id'], 'login'); | |||
header('Location: privada.php'); | |||
exit; | |||
</pre> | |||
=== "Recordar-me" === | === "Recordar-me" === | ||
En cas que l'usuari hagi decidit que l'aplicació el recordi, generarem un token únic que ens servirà per identificar la seva identitat posteriorment en l'inici de sessió amb cookies<pre> | |||
// Generar un token únic i desar-lo a la BD | |||
$token = bin2hex(random_bytes(16)); | |||
</pre>Aquest token, juntament amb la ID de l'usuari les guardarem en galetes<pre> | |||
setcookie('recordar_id', $usuari['id'], time() + 30*24*60*60, '/'); // Recordem la ID de l'usuari | |||
setcookie('recordar_token', $token, time() + 30*24*60*60, '/'); // Recordem el token desat a la BD | |||
</pre>I per últim, desarem el token únic dins la BD en el registre de l'usuari<pre> | |||
$stmt = $pdo->prepare("UPDATE usuaris SET token_recordar = ? WHERE id = ?"); | |||
$stmt->execute([$token, $usuari['id']]); | |||
</pre> | |||
== Inici de sessió mitjançant cookies == | == Inici de sessió mitjançant cookies == | ||
Un cop l'usuari tanca el navegador i torna a obrir-lo, en cas que tingui cookies guardades, el codi les recuperarà<pre> | |||
// AUTO-LOGIN en cas d'haver-hi cookies | |||
if (!esta_autenticat() && isset($_COOKIE['recordar_id'], $_COOKIE['recordar_token'])) { | |||
$id = $_COOKIE['recordar_id']; // Recuperem la ID de l'usuari | |||
$token = $_COOKIE['recordar_token']; // Recuperem el token d'autenticació | |||
</pre>Per verificar que les cookies són correctes, es realitzarà una consulta SQL on es cercarà un usuari amb la ID i el token que s'han desat a la cookie. | |||
Si les cookies són correctes, es trobarà amb l'usuari que havia iniciat sessió.<pre> | |||
// Validar cookies amb la BD | |||
$stmt = $pdo->prepare("SELECT * FROM usuaris WHERE id = ? AND token_recordar = ?"); | |||
$stmt->execute([$id, $token]); | |||
$usuari = $stmt->fetch(); | |||
// Si hi ha un usuari amb la ID i el token que està guardat a la cookie | |||
if ($usuari) { | |||
</pre>Si l’usuari existeix (vol dir que la ID i el token són correctes), regenerarem la ID de sessió per evitar atacs de sessió fixada i definirem els mateixos vectors que s’ha comentat anteriorment. <pre> | |||
session_regenerate_id(true); // Regenerem la ID de sessió | |||
$_SESSION['usuari'] = [ // Iniciem sessió a l'usuari indicant | |||
'id' => $usuari['id'], // El seu ID a la BD | |||
'nom_usuari' => $usuari['nom_usuari'], // El nom d'usuari | |||
'nom_complet' => $usuari['nom_complet'], // El nom complet | |||
'rol' => $usuari['rol'], // El seu rol (usuari, admin) | |||
'autenticat' => true // Definim que està autenticat | |||
]; | |||
</pre>Codi Per últim, registrarem una acció nomenada “auto-login” i redirigirem a l’usuari a la seva pàgina privada<pre> | |||
registrar_activitat($pdo, $usuari['id'], 'auto-login'); // Registrem acció "auto-login" | |||
header('Location: privada.php'); | |||
exit; | |||
</pre>En cas que el valor de les cookies sigui erroni (el token no sigui correcte o la ID no existeixi), no només no permetrem l'inici de sessió sinó que a més a més esborrarem les cookies per evitar més intents<pre> | |||
} else { // Si la consulta no retorna res | |||
// La ID o el token son erronis i no s'inicia sessió. Al ser erroni s'esborra la cookie per evitar nous intents | |||
setcookie('recordar_id', '', time() - 3600, '/'); | |||
setcookie('recordar_token', '', time() - 3600, '/'); | |||
} | |||
</pre> | |||
== Mostra de missatges d'error o èxit == | |||
En cas que l'usuari introdueixi unes credencials errònies, es mostrarà un missatge d'error indicant-ho. | |||
Un exemple de missatge es visualitzaria de la següent forma | |||
[[Fitxer:MissatgeErrorLogin.png|center|miniatura|499x499px]] | |||
== Codi complet == | |||
<pre> | |||
<?php | |||
date_default_timezone_set('Europe/Madrid'); | |||
session_start(); | |||
require 'funcions.php'; | |||
require './connexioBD/connexioRW.php'; | |||
$error = ''; | |||
// Redirigir si ja hi ha una sessió activa | |||
if (esta_autenticat()) { | |||
header('Location: privada.php'); | |||
exit; | |||
} | |||
try{ | |||
// AUTO-LOGIN en cas d'haver-hi cookies | |||
if (!esta_autenticat() && isset($_COOKIE['recordar_id'], $_COOKIE['recordar_token'])) { | |||
$id = $_COOKIE['recordar_id']; // Recuperem la ID de l'usuari | |||
$token = $_COOKIE['recordar_token']; // Recuperem el token d'autenticació | |||
// Validar cookies amb la BD | |||
$stmt = $pdo->prepare("SELECT * FROM usuaris WHERE id = ? AND token_recordar = ?"); | |||
$stmt->execute([$id, $token]); | |||
$usuari = $stmt->fetch(); | |||
// Si hi ha un usuari amb la ID i el token que està guardat a la cookie | |||
if ($usuari) { | |||
session_regenerate_id(true); // Regenerem la ID de sessió | |||
$_SESSION['usuari'] = [ // Iniciem sessió a l'usuari indicant | |||
'id' => $usuari['id'], // El seu ID a la BD | |||
'nom_usuari' => $usuari['nom_usuari'], // El nom d'usuari | |||
'nom_complet' => $usuari['nom_complet'], // El nom complet | |||
'rol' => $usuari['rol'], // El seu rol (usuari, admin) | |||
'autenticat' => true // Definim que està autenticat | |||
]; | |||
registrar_activitat($pdo, $usuari['id'], 'auto-login'); // Registrem acció "auto-login" | |||
header('Location: privada.php'); | |||
exit; | |||
} else { // Si la consulta no retorna res | |||
// La ID o el token son erronis i no s'inicia sessió. Al ser erroni s'esborra la cookie per evitar nous intents | |||
setcookie('recordar_id', '', time() - 3600, '/'); | |||
setcookie('recordar_token', '', time() - 3600, '/'); | |||
} | |||
} | |||
} catch (PDOException $e) { | |||
$error = "Error en iniciar sessió automàticament. Motiu " . $e->getMessage(); | |||
} | |||
// Variables de bloqueig | |||
$max_intents = 3; // Màxim d'intents erronis | |||
$bloqueig_minuts = 2; // Temps de bloqueig (minuts) | |||
try{ | |||
// LOGIN manual | |||
if ($_SERVER['REQUEST_METHOD'] === 'POST') { | |||
// Recuperem les dades del formulari | |||
$nom_usuari = trim($_POST['nom_usuari']); | |||
$contrasenya = $_POST['contrasenya']; | |||
$recordar = isset($_POST['recordar']); // Opció per crear o no galetes | |||
// Recuperem les dades de l'usuari amb el nom d'usuari especificat | |||
$stmt = $pdo->prepare("SELECT * FROM usuaris WHERE nom_usuari = ?"); | |||
$stmt->execute([$nom_usuari]); | |||
$usuari = $stmt->fetch(); | |||
// Verifiquem que l'usuari existeixi | |||
if ($usuari) { | |||
// Comprovar si hi ha bloqueig | |||
if ($usuari['intents_login'] >= $max_intents | |||
&& strtotime($usuari['ultim_intent']) > strtotime("-$bloqueig_minuts minutes")) { | |||
// Calcular temps restant | |||
$ultim_intent_ts = strtotime($usuari['ultim_intent']); //Calculem el temps (l’hora) UNIX de l’últim intent | |||
$final_bloqueig_ts = $ultim_intent_ts + ($bloqueig_minuts * 60); //Calculem el temps (l’hora) UNIX per que s’acabi el bloqueig | |||
$segons_restants = $final_bloqueig_ts - time(); // Calculem els segons restants. Agafem el temps (hora) perquè s’acabi el bloqueig i restem a l’hora actual | |||
$minuts_restants = floor($segons_restants / 60); // Convertim els segons restants a minuts | |||
$segons_restants = $segons_restants % 60; // Recuperem els segons restants després de calcular les hores | |||
$error = "Compte bloquejat temporalment. Torna-ho a provar en {$minuts_restants} minuts i {$segons_restants} segons."; | |||
} else { // Si l'usuari no està bloquejat | |||
if (verificar_contrasenya($contrasenya, $usuari['contrasenya'])) { | |||
// Login correcte, resetajer intents | |||
$stmt = $pdo->prepare("UPDATE usuaris SET intents_login = 0, ultim_intent = NULL WHERE id = ?"); | |||
$stmt->execute([$usuari['id']]); | |||
session_regenerate_id(true); // Protecció session fixation | |||
$_SESSION['usuari'] = [ // Iniciem sessió a l'usuari indicant | |||
'id' => $usuari['id'], // El seu ID a la BD | |||
'nom_usuari' => $usuari['nom_usuari'], // El nom d'usuari | |||
'nom_complet' => $usuari['nom_complet'], // El nom complet | |||
'rol' => $usuari['rol'], // El seu rol (usuari, admin) | |||
'autenticat' => true // Definim que està autenticat | |||
]; | |||
if ($recordar) { // En cas que volguem desar la sessió tot i tancar el navegador | |||
// Generar un token únic i desar-lo a la BD | |||
$token = bin2hex(random_bytes(16)); | |||
setcookie('recordar_id', $usuari['id'], time() + 30*24*60*60, '/'); // Recordem la ID de l'usuari | |||
setcookie('recordar_token', $token, time() + 30*24*60*60, '/'); // Recordem el token desat a la BD | |||
$stmt = $pdo->prepare("UPDATE usuaris SET token_recordar = ? WHERE id = ?"); | |||
$stmt->execute([$token, $usuari['id']]); | |||
} | |||
// Registrem l'acció "login" | |||
registrar_activitat($pdo, $usuari['id'], 'login'); | |||
header('Location: privada.php'); | |||
exit; | |||
} else { | |||
// Si la contrasenya és incorrecte, sumem un intent de login | |||
$stmt = $pdo->prepare("UPDATE usuaris SET intents_login = intents_login + 1, ultim_intent = NOW() WHERE id = ?"); | |||
$stmt->execute([$usuari['id']]); | |||
// Registrar intent fallit | |||
registrar_activitat($pdo, $usuari['id'], 'error-login'); | |||
$error = "Usuari o contrasenya incorrecte"; | |||
} | |||
} | |||
} else { | |||
$error = "L'usuari no existeix"; | |||
} | |||
} | |||
} catch (PDOException $e) { | |||
$error = "Error en iniciar sessió. Motiu: " . $e->getMessage(); | |||
} | |||
?> | |||
<html> | |||
<head> | |||
<meta lang="ca"> | |||
<meta charset="UTF-8"> | |||
<title>Inici de sessió</title> | |||
<link rel="stylesheet" href="./css/login.css"> | |||
</head> | |||
<body> | |||
<div class="login-card"> | |||
<h2>Login</h2> | |||
<?php if ($error): ?> | |||
<div class="error-container"> | |||
<p style='color:red;'><?= $error ?></p> | |||
</div> | |||
<?php endif; ?> | |||
<form method="post"> | |||
<div class="input-group"> | |||
<label>Nom d'usuari:</label> | |||
<input type="text" name="nom_usuari" required> | |||
<a href="registre.php" class="helper-link">No tens usuari? Crea'n un</a> | |||
</div> | |||
<div class="input-group"> | |||
<label>Contrasenya:</label> | |||
<input type="password" name="contrasenya" required> | |||
<a href="recuperacio.php" class="helper-link">He oblidat la contrasenya</a> | |||
</div> | |||
<div class="checkbox-group"> | |||
<input type="checkbox" name="recordar" id="recordar"> | |||
<label for="recordar">Recordar-me</label> | |||
</div> | |||
<input type="submit" value="Entra"> | |||
</form> | |||
<div class="separator"><span>O també pots</span></div> | |||
<div class="mfa-section"> | |||
<a href="mfa.php"><button type="button" class="btn-2fa">Iniciar sessió amb 2FA</button></a> | |||
</div> | |||
</div> | |||
</body> | |||
</html> | |||
</pre> | |||
Revisió de 02:19, 12 gen 2026
Inici de sessió
Quan els usuaris desitjen iniciar sessió, tenen un formulari on poden introduir el seu usuari i contrasenya. A més, tenen l'opció de que l'aplicatiu els recordi desant un token d'autenticació únic dins d'una galeta, si així ho desitja.
Connexió a la BD i inici de sessió (PHP)
Sempre que es treballa amb sessions, el primer que hem de fer és iniciar-la abans d'escriure el codi HTML
session_start();
A més, hem de requerir el fitxer amb les funcions i una connexió a la BD. En aquest cas, si realitzem una autenticació errònia, haurem d'incrementar el número d'intents de login de la BD i per tant requerim la connexió d'escriptura.
require 'funcions.php'; require './connexioBD/connexioRW.php';
Comprovar si està autenticat
En cas que l'usuari està autenticat, el redirigirem directament a la seva pàgina privada. Per comprovar si l'usuari està autenticat farem servir la funció esta_autenticat.
// Redirigir si ja hi ha una sessió activa
if (esta_autenticat()) {
header('Location: privada.php');
exit;
}
Formulari d'inici de sessió
Al formulari es mostra dues entrades de text per introduir el nom d'usuari i la contrasenya. A més, en cas que no s’hagi registrat, tindrà un botó per registrar-se, i si no recorda de les seves credencials, tindrà un botó per poder recuperar la contrasenya.
També es mostra una casella de selecció perque l'aplicatiu el "recordi"
Per últim, també se li dona a l’usuari la oportunitat (sense obligar-lo) a autenticar-se amb 2FA.
El formulari es visualitzaria de la següent forma:

Inici de sessió manual
Obtenció de les dades d'usuari del formulari i la BD
Un cop es respongui el formulari, obtindrem les dades del formulari i realitzarem una consulta per comprovar si el nom d'usuari realment existeix
// Recuperem les dades del formulari
$nom_usuari = trim($_POST['nom_usuari']);
$contrasenya = $_POST['contrasenya'];
$recordar = isset($_POST['recordar']); // Opció per crear o no galetes
// Recuperem les dades de l'usuari amb el nom d'usuari especificat
$stmt = $pdo->prepare("SELECT * FROM usuaris WHERE nom_usuari = ?");
$stmt->execute([$nom_usuari]);
$usuari = $stmt->fetch();
// Verifiquem que l'usuari existeixi
if ($usuari) {
Comprovació d'usuari bloquejat
En cas que existeixi, primerament comprovarem si el compte està bloquejat. Per això comprovarem:
- Que el nombre d’intents d’inici de sessió que s’ha enregistrat a la BD sigui major o igual a $max_intents, en aquest cas 3 intents
- Si l’últim intent d’inici de sessió s’ha produit fa menys de $bloqueig_minuts, en aquest cas 2 minuts
Si es cumpleixen els 2 requisits, el compte estarà bloquejat.
// Comprovar si hi ha bloqueig
if ($usuari['intents_login'] >= $max_intents
&& strtotime($usuari['ultim_intent']) > strtotime("-$bloqueig_minuts minutes")) {
Per fer el càlcul de les dates fem servir la funció interna de PHP strtotime(), la qual retorna la data introduida en temps UNIX (nº de segons des de 1/1/1970).
strtotime($usuari['ultim_intent']) retorna el número de segons que han passat del 1/1/1970 a la data especificada (o dit d'una altra manera, quans segons han passat des de l’1/1/1970 fins el 10/1/2026 a les 15:00)
strtotime("-$bloqueig_minuts minutes") retorna el número de segons que han passat des de l’1/1/1970 fa $bloqueig_minuts minuts (o dit d'una altra manera, quans segons han passat des de l’1/1/1970 fa 2 minuts)
Així, tenim la mateixa “base temporal” amb la que fer càlculs
En cas que el compte estigui bloquejat, mostrarem a l’usuari el temps que queda per desbloquejar-lo.
Per això:
- Calculem el temps (l’hora) UNIX de l’últim intent
- Exemple (Exemple: 15:00 emmagatzemat en format UNIX)
- Calculem el temps (l’hora) UNIX per que s’acabi el bloqueig
- Agafem el temps de l’últim intent i li sumem X minuts en segons, en aquest cas 2 minuts per 60 segons
- Exemple: (Exemple: 15:02, emmagatzemat en format UNIX)
- Calculem els segons restants
- Agafem el temps (hora) perquè s’acabi el bloqueig i restem a l’hora actual
- Exemple: 15:02 - 15:01 = 00:01 (60 segons)
- Convertim els segons restants a minuts i segons
- 80 segons = 1 minut i 20 segons
- Mostrem els minuts i segons restants
// Calcular temps restant
$ultim_intent_ts = strtotime($usuari['ultim_intent']); //Calculem el temps (l’hora) UNIX de l’últim intent
$final_bloqueig_ts = $ultim_intent_ts + ($bloqueig_minuts * 60); //Calculem el temps (l’hora) UNIX per que s’acabi el bloqueig
$segons_restants = $final_bloqueig_ts - time(); // Calculem els segons restants. Agafem el temps (hora) perquè s’acabi el bloqueig i restem a l’hora actual
$minuts_restants = floor($segons_restants / 60); // Convertim els segons restants a minuts
$segons_restants = $segons_restants % 60; // Recuperem els segons restants després de calcular les hores
$error = "Compte bloquejat temporalment. Torna-ho a provar en {$minuts_restants} minuts i {$segons_restants} segons.";
Per tant el fluxe del codi seria el següent:
- L’usuari s’equivoca 3 vegades
- Intenta accedir una 4ta, no pot (3 intents_login, 3 segons últim intent)
- Espera 2 minuts (3 intents_login, 2 minuts últim intent, pot accedir)
- Es torna a equivocar una 5na vegada (3 intents_login, 3 segons últim intent)
- Torna a esperar 2 minuts
- Accedeix correctament
Comprovació de contrasenya
Un cop verificat que l'usuari no està bloquejat, comprovarem que la contrasenya sigui correcta amb la funció verificar_contrasenya.
if (verificar_contrasenya($contrasenya, $usuari['contrasenya'])) {
En cas que la contrasenya sigui errònia, augmentarem en 1 els número d'intents d'autenticació, registrarem una acció a la taula activitat de nom "error-login" i registrarem un missatge d'error
// Si la contrasenya és incorrecte, sumem un intent de login $stmt = $pdo->prepare("UPDATE usuaris SET intents_login = intents_login + 1, ultim_intent = NOW() WHERE id = ?"); $stmt->execute([$usuari['id']]);
// Registrar intent fallit registrar_activitat($pdo, $usuari['id'], 'error-login'); $error = "Usuari o contrasenya incorrecte";
Inici de sessió
Si la contrasenya és correcte, actualitzarem el nombre d’intents de login a 0
// Login correcte, resetajer intents
$stmt = $pdo->prepare("UPDATE usuaris SET intents_login = 0, ultim_intent = NULL WHERE id = ?");
$stmt->execute([$usuari['id']]);
Seguidament, regenerarem la ID de sessió per evitar atacs de sessió fixada
session_regenerate_id(true); // Protecció session fixation
Per acabar creant un vector de nom “usuari” dins de la sessió amb els següents vectors:
- ID de l’usuari dins la BD
- Nom d’usuari
- Nom complet
- Rol, per definir si és administrador o no i quin tipus
- Autenticat, per definir que està autenticat
$_SESSION['usuari'] = [ // Iniciem sessió a l'usuari indicant
'id' => $usuari['id'], // El seu ID a la BD
'nom_usuari' => $usuari['nom_usuari'], // El nom d'usuari
'nom_complet' => $usuari['nom_complet'], // El nom complet
'rol' => $usuari['rol'], // El seu rol (usuari, admin)
'autenticat' => true // Definim que està autenticat
];
Un cop autenticat, registrarem una acció a la taula activitat de nom "login" i redirigirem l'usuari a la seva pàgina privada
// Registrem l'acció "login" registrar_activitat($pdo, $usuari['id'], 'login'); header('Location: privada.php'); exit;
"Recordar-me"
En cas que l'usuari hagi decidit que l'aplicació el recordi, generarem un token únic que ens servirà per identificar la seva identitat posteriorment en l'inici de sessió amb cookies
// Generar un token únic i desar-lo a la BD $token = bin2hex(random_bytes(16));
Aquest token, juntament amb la ID de l'usuari les guardarem en galetes
setcookie('recordar_id', $usuari['id'], time() + 30*24*60*60, '/'); // Recordem la ID de l'usuari setcookie('recordar_token', $token, time() + 30*24*60*60, '/'); // Recordem el token desat a la BD
I per últim, desarem el token únic dins la BD en el registre de l'usuari
$stmt = $pdo->prepare("UPDATE usuaris SET token_recordar = ? WHERE id = ?"); $stmt->execute([$token, $usuari['id']]);
Inici de sessió mitjançant cookies
Un cop l'usuari tanca el navegador i torna a obrir-lo, en cas que tingui cookies guardades, el codi les recuperarà
// AUTO-LOGIN en cas d'haver-hi cookies
if (!esta_autenticat() && isset($_COOKIE['recordar_id'], $_COOKIE['recordar_token'])) {
$id = $_COOKIE['recordar_id']; // Recuperem la ID de l'usuari
$token = $_COOKIE['recordar_token']; // Recuperem el token d'autenticació
Per verificar que les cookies són correctes, es realitzarà una consulta SQL on es cercarà un usuari amb la ID i el token que s'han desat a la cookie. Si les cookies són correctes, es trobarà amb l'usuari que havia iniciat sessió.
// Validar cookies amb la BD
$stmt = $pdo->prepare("SELECT * FROM usuaris WHERE id = ? AND token_recordar = ?");
$stmt->execute([$id, $token]);
$usuari = $stmt->fetch();
// Si hi ha un usuari amb la ID i el token que està guardat a la cookie
if ($usuari) {
Si l’usuari existeix (vol dir que la ID i el token són correctes), regenerarem la ID de sessió per evitar atacs de sessió fixada i definirem els mateixos vectors que s’ha comentat anteriorment.
session_regenerate_id(true); // Regenerem la ID de sessió $_SESSION['usuari'] = [ // Iniciem sessió a l'usuari indicant
'id' => $usuari['id'], // El seu ID a la BD 'nom_usuari' => $usuari['nom_usuari'], // El nom d'usuari 'nom_complet' => $usuari['nom_complet'], // El nom complet 'rol' => $usuari['rol'], // El seu rol (usuari, admin) 'autenticat' => true // Definim que està autenticat];
Codi Per últim, registrarem una acció nomenada “auto-login” i redirigirem a l’usuari a la seva pàgina privada
registrar_activitat($pdo, $usuari['id'], 'auto-login'); // Registrem acció "auto-login" header('Location: privada.php'); exit;
En cas que el valor de les cookies sigui erroni (el token no sigui correcte o la ID no existeixi), no només no permetrem l'inici de sessió sinó que a més a més esborrarem les cookies per evitar més intents
} else { // Si la consulta no retorna res // La ID o el token son erronis i no s'inicia sessió. Al ser erroni s'esborra la cookie per evitar nous intents
setcookie('recordar_id', '', time() - 3600, '/'); setcookie('recordar_token', '', time() - 3600, '/');}
Mostra de missatges d'error o èxit
En cas que l'usuari introdueixi unes credencials errònies, es mostrarà un missatge d'error indicant-ho.
Un exemple de missatge es visualitzaria de la següent forma

Codi complet
<?php
date_default_timezone_set('Europe/Madrid');
session_start();
require 'funcions.php';
require './connexioBD/connexioRW.php';
$error = '';
// Redirigir si ja hi ha una sessió activa
if (esta_autenticat()) {
header('Location: privada.php');
exit;
}
try{
// AUTO-LOGIN en cas d'haver-hi cookies
if (!esta_autenticat() && isset($_COOKIE['recordar_id'], $_COOKIE['recordar_token'])) {
$id = $_COOKIE['recordar_id']; // Recuperem la ID de l'usuari
$token = $_COOKIE['recordar_token']; // Recuperem el token d'autenticació
// Validar cookies amb la BD
$stmt = $pdo->prepare("SELECT * FROM usuaris WHERE id = ? AND token_recordar = ?");
$stmt->execute([$id, $token]);
$usuari = $stmt->fetch();
// Si hi ha un usuari amb la ID i el token que està guardat a la cookie
if ($usuari) {
session_regenerate_id(true); // Regenerem la ID de sessió
$_SESSION['usuari'] = [ // Iniciem sessió a l'usuari indicant
'id' => $usuari['id'], // El seu ID a la BD
'nom_usuari' => $usuari['nom_usuari'], // El nom d'usuari
'nom_complet' => $usuari['nom_complet'], // El nom complet
'rol' => $usuari['rol'], // El seu rol (usuari, admin)
'autenticat' => true // Definim que està autenticat
];
registrar_activitat($pdo, $usuari['id'], 'auto-login'); // Registrem acció "auto-login"
header('Location: privada.php');
exit;
} else { // Si la consulta no retorna res
// La ID o el token son erronis i no s'inicia sessió. Al ser erroni s'esborra la cookie per evitar nous intents
setcookie('recordar_id', '', time() - 3600, '/');
setcookie('recordar_token', '', time() - 3600, '/');
}
}
} catch (PDOException $e) {
$error = "Error en iniciar sessió automàticament. Motiu " . $e->getMessage();
}
// Variables de bloqueig
$max_intents = 3; // Màxim d'intents erronis
$bloqueig_minuts = 2; // Temps de bloqueig (minuts)
try{
// LOGIN manual
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Recuperem les dades del formulari
$nom_usuari = trim($_POST['nom_usuari']);
$contrasenya = $_POST['contrasenya'];
$recordar = isset($_POST['recordar']); // Opció per crear o no galetes
// Recuperem les dades de l'usuari amb el nom d'usuari especificat
$stmt = $pdo->prepare("SELECT * FROM usuaris WHERE nom_usuari = ?");
$stmt->execute([$nom_usuari]);
$usuari = $stmt->fetch();
// Verifiquem que l'usuari existeixi
if ($usuari) {
// Comprovar si hi ha bloqueig
if ($usuari['intents_login'] >= $max_intents
&& strtotime($usuari['ultim_intent']) > strtotime("-$bloqueig_minuts minutes")) {
// Calcular temps restant
$ultim_intent_ts = strtotime($usuari['ultim_intent']); //Calculem el temps (l’hora) UNIX de l’últim intent
$final_bloqueig_ts = $ultim_intent_ts + ($bloqueig_minuts * 60); //Calculem el temps (l’hora) UNIX per que s’acabi el bloqueig
$segons_restants = $final_bloqueig_ts - time(); // Calculem els segons restants. Agafem el temps (hora) perquè s’acabi el bloqueig i restem a l’hora actual
$minuts_restants = floor($segons_restants / 60); // Convertim els segons restants a minuts
$segons_restants = $segons_restants % 60; // Recuperem els segons restants després de calcular les hores
$error = "Compte bloquejat temporalment. Torna-ho a provar en {$minuts_restants} minuts i {$segons_restants} segons.";
} else { // Si l'usuari no està bloquejat
if (verificar_contrasenya($contrasenya, $usuari['contrasenya'])) {
// Login correcte, resetajer intents
$stmt = $pdo->prepare("UPDATE usuaris SET intents_login = 0, ultim_intent = NULL WHERE id = ?");
$stmt->execute([$usuari['id']]);
session_regenerate_id(true); // Protecció session fixation
$_SESSION['usuari'] = [ // Iniciem sessió a l'usuari indicant
'id' => $usuari['id'], // El seu ID a la BD
'nom_usuari' => $usuari['nom_usuari'], // El nom d'usuari
'nom_complet' => $usuari['nom_complet'], // El nom complet
'rol' => $usuari['rol'], // El seu rol (usuari, admin)
'autenticat' => true // Definim que està autenticat
];
if ($recordar) { // En cas que volguem desar la sessió tot i tancar el navegador
// Generar un token únic i desar-lo a la BD
$token = bin2hex(random_bytes(16));
setcookie('recordar_id', $usuari['id'], time() + 30*24*60*60, '/'); // Recordem la ID de l'usuari
setcookie('recordar_token', $token, time() + 30*24*60*60, '/'); // Recordem el token desat a la BD
$stmt = $pdo->prepare("UPDATE usuaris SET token_recordar = ? WHERE id = ?");
$stmt->execute([$token, $usuari['id']]);
}
// Registrem l'acció "login"
registrar_activitat($pdo, $usuari['id'], 'login');
header('Location: privada.php');
exit;
} else {
// Si la contrasenya és incorrecte, sumem un intent de login
$stmt = $pdo->prepare("UPDATE usuaris SET intents_login = intents_login + 1, ultim_intent = NOW() WHERE id = ?");
$stmt->execute([$usuari['id']]);
// Registrar intent fallit
registrar_activitat($pdo, $usuari['id'], 'error-login');
$error = "Usuari o contrasenya incorrecte";
}
}
} else {
$error = "L'usuari no existeix";
}
}
} catch (PDOException $e) {
$error = "Error en iniciar sessió. Motiu: " . $e->getMessage();
}
?>
<html>
<head>
<meta lang="ca">
<meta charset="UTF-8">
<title>Inici de sessió</title>
<link rel="stylesheet" href="./css/login.css">
</head>
<body>
<div class="login-card">
<h2>Login</h2>
<?php if ($error): ?>
<div class="error-container">
<p style='color:red;'><?= $error ?></p>
</div>
<?php endif; ?>
<form method="post">
<div class="input-group">
<label>Nom d'usuari:</label>
<input type="text" name="nom_usuari" required>
<a href="registre.php" class="helper-link">No tens usuari? Crea'n un</a>
</div>
<div class="input-group">
<label>Contrasenya:</label>
<input type="password" name="contrasenya" required>
<a href="recuperacio.php" class="helper-link">He oblidat la contrasenya</a>
</div>
<div class="checkbox-group">
<input type="checkbox" name="recordar" id="recordar">
<label for="recordar">Recordar-me</label>
</div>
<input type="submit" value="Entra">
</form>
<div class="separator"><span>O també pots</span></div>
<div class="mfa-section">
<a href="mfa.php"><button type="button" class="btn-2fa">Iniciar sessió amb 2FA</button></a>
</div>
</div>
</body>
</html>