Session Login met Cookie onthouden

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Danny von Gaal

Danny von Gaal

19/01/2016 16:05:27
Quote Anchor link
Hallo mensen ben ik weer met een vraagje.

Ik heb een website gemaakt met een loginsysteem dmv $_SESSIONS maar nu klaagde sommige mensen dat ze iedere keer uitgelogd waren. Ik vond het zelf ook wel vervelend dus ik heb nu een $_COOKIE variant verzonnen maar omdat het mijn eerste keer is heb ik daar toch wel twijfels over.

Daarom hoor ik hier graag op of aanmerkingen. (In mijn $_SESSION sla ik de userid van het ingelogde account op waardoor je op iedere pagina bent aangemeld.)

Ten eerste heb ik in mijn database een tabel gemaakt met de kolommen:
- id (PRIMARY,AUTO_INCREMENT)
- userid
- cookiehash (UNIQUE)
- timestamp

Vervolgens heb ik dit in mijn inlogscript verwerkt:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
// Als "Onthoud Mij" is aangeklikt
                        if(isset($_POST['onthoudmij'])) {
                            
                            $cookiehash = md5(uniqid(microtime(), true));
                            
                            $SQLSelectLoginSessies = "SELECT userid
                                                      FROM login_sessies
                                                      WHERE userid = '"
. $row['id'] . "'";
                            $ResultLoginSessies = $conn->query($SQLSelectLoginSessies);
                            if ($ResultLoginSessies->num_rows > 0) {
                                
                                $sqlUpdateLoginSessies = "UPDATE login_sessies
                                                          SET cookiehash='"
. $cookiehash . "'
                                                          WHERE userid='"
. $row['id'] . "'";
                                if ($conn->query($sqlUpdateLoginSessies) === TRUE) {
                                    
                                    setcookie("Onthoudmij", $cookiehash, time() + (86400 * 30), "/", null, true, true); // 30 dagen
                                    header('Location: ' . $terug . '');
                                    exit;
                                    
                                }
else {
                                    
                                    header('Location: ' . $pagina . '&code=0003'); // Kan cookie niet zetten.
                                    exit;
                                    
                                }
                                
                            }
else {
                                
                                $SQLInsertLoginSessies = "INSERT INTO login_sessies (userid,cookiehash)
                                                          VALUES ('"
. $row['id'] . "','" . $cookiehash . "')";
                                if ($conn->query($SQLInsertLoginSessies) === TRUE) {
                                    
                                    setcookie("Onthoudmij", $cookiehash, time() + (86400 * 30), "/", null, true, true); // 30 dagen
                                    header('Location: ' . $terug . '');
                                    exit;
                                    
                                }
else {
                                    
                                    header('Location: ' . $pagina . '&code=0003'); // Kan cookie niet zetten.
                                    exit;
                                    
                                }
                                
                            }                            
                            
                            
                        }

?>


En bovenaan mijn index.php pagina dit:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
// Check voor Cookie
if(isset($_COOKIE['Onthoudmij'])) {
    
    $CheckCookie = mysqli_real_escape_string($conn,$_COOKIE['Onthoudmij']);
    
    $SQLCheckCookie = "SELECT userid,cookiehash
                       FROM login_sessies
                       WHERE cookiehash = '"
. $CheckCookie . "'";
    $ResultCheckCookie = $conn->query($SQLCheckCookie);
    if ($ResultCheckCookie->num_rows > 0) {
        
        while($RowLoginSessies = $ResultCheckCookie->fetch_assoc()) {
        
            $_SESSION['user'] = $RowLoginSessies['userid'];
            
        }
        
    }
        
}

?>



Dus wanneer er een cookie is gevonden op de clïent met een hash die overeenkomt met een hash in mijn tabel dan wordt dezelfde sessie gemaakt als wanneer je voorheen met sessies inlogde.
Doordat de kolom cookiehash in mijn tabel UNIQUE moet zijn kan je nooit een andere userid krijgen.
Gewijzigd op 19/01/2016 16:22:16 door Danny von Gaal
 
PHP hulp

PHP hulp

23/04/2024 18:20:36
 
- Ariën  -
Beheerder

- Ariën -

19/01/2016 16:07:55
Quote Anchor link
Zou je jouw topictitel aan willen passen zodat deze je vraag- of probleemstelling aanduidt?

Alvast bedankt!
 
Ben van Velzen

Ben van Velzen

19/01/2016 16:18:38
Quote Anchor link
Grappig, want sessies gebruiken ook gewoon cookies. In plaats van te proberen een andere manier te verzinnen is het veel verstandiger om de reden van het uitloggen te achterhalen.
 
Danny von Gaal

Danny von Gaal

19/01/2016 16:20:40
Quote Anchor link
Ben van Velzen op 19/01/2016 16:18:38:
Grappig, want sessies gebruiken ook gewoon cookies. In plaats van te proberen een andere manier te verzinnen is het veel verstandiger om de reden van het uitloggen te achterhalen.


Hallo Ben,

Ik weet dat je de session lifetime kan verlengen maar dat leek mij zo onveilig??
 
Thomas van den Heuvel

Thomas van den Heuvel

19/01/2016 16:52:49
Quote Anchor link
@Danny, zou je het niet omdraaien? Controleer eerst of $_SESSION['user'] bestaat, en dan (anders) of $_COOKIE['Onthoudmij'] bestaat om een sessie eventueel "door te starten"? EDIT anders is dit niet nodig, je sessie bestaat nog.

Ook zou ik, als je zo'n doorstart maakt, de cookiehash verversen. EDIT: of misschien nog beter, ververs deze zolang je sessie bestaat.

Daarnaast zou je kunnen overwegen om een langere hash te pakken zodat deze moeilijker te raden is.

Zo te zien maak je al gebruik van HTTPS? Volgens mij is deze opzet dan redelijk veilig (dataverkeer kan niet ontcijferd worden en cookies kunnen alleen via HTTP ingesteld worden), but don't take my word for it.

Op dit moment is de hash het enige identificerende attribuut. Afhankelijk van de situatie zou je ook een IP of user agent kunnen verwerken in/bij de hash.
Gewijzigd op 19/01/2016 16:54:19 door Thomas van den Heuvel
 
Danny von Gaal

Danny von Gaal

19/01/2016 20:57:56
Quote Anchor link
@Thomas: Cool om te horen dat de methode die ik heb verzonnen redelijk veilig is.

Ik zal inderdaad boven de isset($_COOKIE eerst een check op mijn user session maken. En ik gebruik inderdaad HTTPS:// en via php.net zag ik dat je cookies op HTTPS kan afdwingen. Ik weet echter niet of dit nog nadelige gevolgen heeft.

En ik zat er ook aan te denken om de hash bij iedere opvraag van een pagina te verversen maar ik was bang dat de load naar de database dan te groot zal worden. Of kan die wel veel hebben?

Momenteel gebruik ik
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
<?phpmd5(uniqid(microtime(), true))?>
voor de hash maar welke encryptie methode stel jij voor? Het lijkt mij nu al niet dat iemand dit zal raden aangezien het op niets gebasseerd is (een naam bijvoorbeeld).
 
Ward van der Put
Moderator

Ward van der Put

20/01/2016 09:08:30
Quote Anchor link
Danny von Gaal op 19/01/2016 20:57:56:
Momenteel gebruik ik
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
<?phpmd5(uniqid(microtime(), true))?>
voor de hash maar welke encryptie methode stel jij voor? Het lijkt mij nu al niet dat iemand dit zal raden aangezien het op niets gebasseerd is (een naam bijvoorbeeld).

De functie uniqid() genereert een pseudo-aselecte ID op basis van de systeemklok. Genereer maar eens 100 ID's in een for-loop en je zult zien dat het eerste gedeelte van de string oploopt met de tijd.

Vandaar deze waarschuwing:

Warning This function does not create random nor unpredictable strings. This function must not be used for security purposes. Use a cryptographically secure random function/generator and cryptographically secure hash functions to create unpredictable secure IDs.

Aangezien uniqid() al een string gebaseerd op de systeemklok geeft, heeft het niet zoveel zin om de $prefix in de eerste parameter met microtime() op de systeemtijd te zetten. Je doet dan namelijk iets dat de functie zelf al doet:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
<?php
$cookiehash
= md5(uniqid(microtime(), true));
?>


Je kunt hier beter met bijvoorbeeld mt_rand() een aselect getal genereren:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
<?php
$cookiehash
= md5(uniqid(mt_rand(), true));
?>


Je gebruikt al true voor de tweede parameter $more_entropy van uniqid() om de ID minder voorspelbaar te maken. Je kunt daar zelf een flinke schep bovenop doen door een aselect getal toe te voegen:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
<?php
$cookiehash
= md5(uniqid(mt_rand(), true) . mt_rand());
?>


Aangezien mt_rand() al is gebruikt voor de prefix, zou ik echter een andere functie dan mt_rand() gebruiken voor deze postfix, bijvoorbeeld:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
<?php
$cookiehash
= md5(uniqid(mt_rand(), true) . bin2hex(openssl_random_pseudo_bytes(128)));
?>


Tot slot zit de hashfunctie md5() nog in de weg. Die kun je wel gebruiken voor interne hashes, maar beter niet meer voor beveiligingsdoeleinden. Op zijn minst gebruik je SHA-1:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
<?php
$cookiehash
= sha1(uniqid(mt_rand(), true) . bin2hex(openssl_random_pseudo_bytes(128)));
?>


Nog beter is een langere en sterkere variant uit de SHA-familie:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
<?php
$cookiehash
= hash('sha512', uniqid(mt_rand(), true) . bin2hex(openssl_random_pseudo_bytes(128)));
?>
 
Thomas van den Heuvel

Thomas van den Heuvel

20/01/2016 12:46:10
Quote Anchor link
Mja, elk systeem heeft zijn zwakste schakel.

Ook mt_rand() is niet cryptographically secure.

In dat artikel wordt het concept nonce aangehaald, wat wellicht een idee is om te gebruiken, maar heeft misschien ook haken en ogen.

In dit artikel staat wel een aardig idee voor een IP-loze oplossing met net iets meer "identificatie" dan simpelweg een hash, en ook in de user comments staan goede aanvullingen, met name een veilig(er) alternatief voor mt_rand():
lezen van /dev/urandom (unix).

Op PHP.net worden ook enkele alternatieven genoemd: random_int(), random_bytes() of openssl_random_pseudo_bytes() (die hierboven al gebruikt wordt) en ook daar wordt in de comments /dev/urandom aangehaald.
 
Ward van der Put
Moderator

Ward van der Put

20/01/2016 13:20:43
Quote Anchor link
Eens hoor Thomas, maar in dit specifieke geval moeten we dit er eigenlijk nog bij betrekken:
Danny von Gaal op 19/01/2016 16:05:27:
Ten eerste heb ik in mijn database een tabel gemaakt met de kolommen:
- id (PRIMARY,AUTO_INCREMENT)
- userid
- cookiehash (UNIQUE)
- timestamp
[...]
Doordat de kolom cookiehash in mijn tabel UNIQUE moet zijn kan je nooit een andere userid krijgen.

En dat in combinatie met de query:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
<?php
$SQLCheckCookie
= "SELECT userid,cookiehash
                   FROM login_sessies
                   WHERE cookiehash = '"
. $CheckCookie . "'";
?>

Dat is namelijk niet zomaar een random ID, maar een GUID of UUID. Ik begrijp wel waarom Danny uniqid() heeft gebruikt voor het genereren van die Global/Universal Unique ID, maar ik denk dat de functie UUID() van MySQL in een CHAR(36)-kolom daarvoor een beter alternatief is.

Nou is UUID() wel uniek, maar helaas ook voorspelbaar en daardoor te raden. Als je daar echter nog een (pseudo)random ID aan toevoegt — of naast zet, voor mijn part in een aparte kolom tot het maximum CHAR(255) — dan heb je een oplossing die zowel uniek als onvoorspelbaar is.
 
Thomas van den Heuvel

Thomas van den Heuvel

20/01/2016 14:13:36
Quote Anchor link
Fair enough. Op welke moment(en) zou dit cookie (hash + levensduur) ververst moeten worden? Enkel als deze gebruikt wordt om een sessie door te starten? En is het bijvoorbeeld (ook) verstandig om elk request session_regenerate_id(true) te gebruiken?

Ik vind de discussie zeker interessant, maar wil tegelijkertijd eigenlijk ook wel naar een soort van concrete aanbeveling toe (en Danny waarschijnlijk ook :D).
 
Danny von Gaal

Danny von Gaal

20/01/2016 14:23:47
Quote Anchor link
Ik heb de hash nu vervangen voor
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
<?php
$cookiehash
= hash('sha512', uniqid(mt_rand(), true) . bin2hex(openssl_random_pseudo_bytes(128)));
?>


Maar om nog even terug te komen op dat de hash steeds wordt vernieuwd? Dan moet ik bij iedere pagina opvraag dus mijn database updaten en een nieuwe cookie setten? Zal dat uiteindelijk met veel gebruikers niet teveel verkeer generen en mijn database belasten? Of doen alle sites dat?

Ik zou het wel fijn vinden want dan kan ik laten zien wanneer de gebruiker voor het laatst online was. :)
 
Ward van der Put
Moderator

Ward van der Put

20/01/2016 15:45:29
Quote Anchor link
Laten we dan eens kijken wat we nou eigenlijk doen.

Elke sessie heeft een eigen, unieke sessie-ID. De cookie-UUID maakt daarvan een geldige sessie — of meer precies: een sessie met een bekende user-ID.

Zou je de cookie-UUID nooit veranderen, dan krijg je iets dat lijkt op een eeuwigdurende sessie. Dat is het echter niet: het is een keten van opeenvolgende sessies met eigen sessie-ID's. Alles dat je daarbij in eerdere sessie hebt opgeslagen, gaat verloren. De nieuwe sessie bevat in deze opzet namelijk enkel en alleen de user-ID.

Er is dus vooral een directe één-op-één-relatie tussen de cookie-ID en de user-ID. Dat je het geheel beheert via sessies, is daarin maar bijzaak.

Voor de beveiliging betekent dit dat je dit cookie slechts beperkt moet vertrouwen: het geeft het vermoeden dat je te maken hebt met de user-ID van een terugkerende bezoeker. Niet meer, niet minder.

De rest van de beveiliging moet je daarop voortbouwen: wanneer is dat vermoeden voldoende en wanneer niet? Bijvoorbeeld voor het wijzigen van het wachtwoord, een e-mailadres en andere account- of gebruikersgegevens zul je dan opnieuw inloggen verplicht moeten stellen. Sommige webwinkels gebruiken vergelijkbare criteria: je kunt als terugkerende klant direct gaan winkelen, maar als je de order wilt bevestigen of wilt afrekenen, moet je even opnieuw inloggen.
 
Danny von Gaal

Danny von Gaal

20/01/2016 19:10:13
Quote Anchor link
Momenteel set ik een cookie die 30 dagen geldig is. Maar het is me al opgevallen dat iedere keer dat ik weer op de site kom de cookie niet vanzelf wordt bijgewerkt. Uiteindelijk zal je dus na 30 dagen weer afgemeld zijn zelfs als je er elke dag komt. Dat zou ik dus ook voorkomen als ik iedere keer een nieuwe cookie set.
 
Danny von Gaal

Danny von Gaal

23/01/2016 22:13:36
Quote Anchor link
Helaas ben ik er achter gekomen dat deze methode helaas niet werkt.
Wanneer je namelijk op je pc en telefoon inlogt en je kiest bij beide voor onthouden dan veranderd de code in de db waardoor je op het andere apparaat automatisch bent uitgelogd. :(

Iemand ideeen?
 
Ben van Velzen

Ben van Velzen

23/01/2016 22:40:52
Quote Anchor link
Gewoon met een koppeltabel werken zodat je meerdere "sessies" aan 1 gebruiker kunt hangen? Dan kun je per entry ook een maximale levensduur hangen zodat je bijvoorbeeld na een maand of een jaar zonder gebruik van een bepaalde hash deze kan uitwissen.
 



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.