Du må være registrert og logget inn for å kunne legge ut innlegg på freak.no
X
LOGG INN
... eller du kan registrere deg nå
Dette nettstedet er avhengig av annonseinntekter for å holde driften og videre utvikling igang. Vi liker ikke reklame heller, men alternativene er ikke mange. Vær snill å vurder å slå av annonseblokkering, eller å abonnere på en reklamefri utgave av nettstedet.
  50 8419
God kveld folkens.

Er det noen som er gode på SQL og kan svare på noen spørsmål her og kanskje komme med en løsning.


Sak:

Jeg har en app som tar imot input igjennom et skjema. Skjema lagres i databasen med AJAX / PHP.
Brukeren kan velge å slette sin input ved hjelp av Epost og SecretKey som de oppretter.

Når de skal slette denne går dette igjennom dette scriptet:

Kode

$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}


$delete = $_POST['delete'];
$skey = $_POST['skey'];

$query = mysqli_query($conn, "SELECT * FROM form WHERE email='$delete'");

$sql = "DELETE FROM form WHERE secretKey='$skey' AND email='$delete'";

if(mysqli_num_rows($query) < 0) {
	echo "The email: " . $_POST['delete'] . " does not exist in our records";
}
elseif(!mysqli_query($conn, $sql)) {
	echo "Could not delete data!";
}
else {
	echo "You have deleted your information from our site. We hope you will come back!";
}


mysqli_close($conn);

Problem:

Problemet er som følger at når man KUN skriver inn epost og ikke secretKey for å slette vil den fortsatt slette personen fra databasen.
Denne skal KUN fungere om Epost OG secretKey de skriver inn er det samme som i databaseraden.

Samtidig kjører else statementen om jeg bare skriver inn secretKey og ikke epost som ikke gjør noen ting, men echoet sier at denne er slettet, noe det ikke er.




Noen som kan se denne koden og se hva som kan være feil her? Har nå prøvd å googla og gjort om på ting i over 4 timer, men får det absoutt ikke til.
Limited edition
Moff's Avatar
For å svare på spørsmålet først; jeg kan ikke se noen grunn til at dette problemet skal oppstå på bakgrunn av selve spørringen:

Kode

DELETE FROM form WHERE secretKey='$skey' AND email='$delete'
Dette vil slette alle rader fra tabellen "form" som har secretKey satt til $skey og email satt til $delete. Det er derfor ting som tyder på at problemet ligger et annet sted. Det første jeg hadde gjort her er å dobbeltsjekke hva som faktisk skjer i databasen. Er du helt sikker på at det slettes rader som ikke skal slettes? Hvordan vet du at dette skjer?

Gi oss gjerne en liten oversikt over noe eksempeldata fra tabellen din før og etter, sånn at vi kan sammenligne og se nøyaktig hva det er som foregår.

Men problemet du opplever her er ubetydelig sammenlignet med hvilke problemer du vil få hvis du publiserer denne koden på internett. Du har en gedigen SQL-exploit her. Det du gjør er å ta input fra en klient via $_POST-arrayet, og så dytter du denne inputen rett inn i en SQL-spørring uten å sjekke hva det er brukeren har skrevet inn.

Hvis jeg har lyst til å være litt ugrei med deg, så vil jeg gå inn på tjenesten din og oppgi at min e-postadresse er dette:

Kode

';drop table form;/*
Spørringen din ser slik ut:

Kode

SELECT * FROM form WHERE email='$delete'
Når skriptet ditt skal behandle min "e-postadresse" ovenfor, så blir spørringen da slik:

Kode

SELECT * FROM form WHERE email='';drop table form;/*'
Først kjører vi en SELECT med blank e-postadresse, og deretter kjører vi en DROP TABLE, som sletter hele tabellen og alt innhold i den. Avhengig av hvilke rettigheter MySQL-brukeren din har, så kan man gjøre veldig mye tullball her.

Det du må gjøre er å sørge for at input er vasket og ikke kan tolkes som kommandoer i spørringen din. Alle gode SQL-biblioteker skal støtte det vi kaller prepared statements, som er en trygg teknikk for å skrive spørringen først, og deretter fylle inn variablene du skal bruke. Personlig bruker jeg PDO i stedet for MySQLi, men så vidt jeg kan se så er syntaksen for MySQLi slik:

Kode

$query= $conn->prepare("SELECT * FROM form WHERE email= ?");
$query->bind_param("s", $_POST['delete']);
$query->execute();

$result = $query->get_result();
$query->close();

if($result->num_rows == 0) {
// 0 rader funnet
}
If-blokken din er også litt merkelig satt sammen. Vanligvis så pleier vi ikke å mikse forskjellige conditions i samme if-blokk. Det skal fungere slik du har skrevet det, men det er ikke helt stuerent. Det jeg mener er at du først har en sjekk om om SELECT-statementen ga resultater, deretter har du en elseif som både kjører DELETE-statementen og sjekker om den gikk bra, og til slutt har du en else.

Det vil si at du har flere conditions som strengt tatt ikke har noe med hverandre å gjøre, og det er også derfor du kan få litt pussige resultater. Det er også verdt å nevne at mysqli_query returnerer false kun hvis noe har gått galt, ikke hvis spørringen kjørte uten noen affected rows. Du vil derfor se meldingen om at data har blitt slettet så lenge DELETE-statementen ble kjørt, uansett om noe ble slettet eller ikke. For å unngå dette må du se på resultatet av spørringen og num_rows, som demonstrert i kodeeksempelet mitt ovenfor. I SQL så er det generelt ikke bra å basere seg på noe annet enn row count (affected rows) for å sjekke hva som har skjedd.

Et siste tips jeg kan komme med, som du bør tenke over, er at det kan være lurt å gi brukerne dine falske opplysninger. I et system som dette, så ville jeg gitt tilbakemelding om at informasjonen er slettet selv om den ikke har blitt det - fordi det gjør det umulig for folk å mine data fra deg. Ta for eksempel reset-passord-funksjoner på ulike nettsider; når de sier ifra at e-postadressen du skriver inn ikke eksisterer i deres system, så kan det fortelle deg alt du trenger å vite. Er personen du er på jakt etter registrert her eller ikke? Se for deg at du har kommet over privat-e-posten til en politiker, og du finner ut at denne adressen er i bruk på et snuskete nettsted, for eksempel. Slike ting trenger du ikke å gi folk informasjon om hvis du ikke må.
Sist endret av Moff; 25. april 2019 kl. 22:00.
Takk for veldig godt utfyllende svar Moff.
Er ikke selv dreven i PHP så jeg har satt sammen det lille jeg kan av PHP og mysql.

En liten test case jeg gjør da er som følger:

Jeg oppretter en infoblokk med epost og secretKey: 1234.
Dette kommer inn i databasen og vises også i appen.

Når jeg velger slett så skriver jeg inn eposten og raden er slettet fra databasen helt og vises ikke lengre i appen. Her trenger jeg ikke å skrive inn secretKey, men skriver jeg inn secretKey blir den også slettet.

Skriver jeg inn BARE secretKey og ikke Epost får jeg beskjeden: Could not delete data og ingenting er slettet.


SKal sjekke litt ut det du skriver her. Vil selvfølgelig ikke ha noen til å kjøre sql spørringer i formen min.
Men fortsatt så får jeg feilen om at den ikke sjekker email OG secretKey, men bare email.
Limited edition
Moff's Avatar
Det du ev. kan gjøre for å teste dette skikkelig er at du logger spørringen din før du kjører den. Ta for eksempel noe sånt:

Kode

$sql = "DELETE FROM form WHERE secretKey='$skey' AND email='$delete'";

echo $sql . '<br><br>';
Dette bør føre til at hele spørringen med variabler blir dumpet rett ut i nettleseren slik at alle kan se hva det er som skjer. (De to <br>-ene er bare HTML-kode for å separere output fra den meldingen som kommer etterpå, når skriptet er ferdig å kjøre. Gjør det litt mer lesbart.)

Et annet alternativ er at du prøver å overvåke trafikken med Fiddler. Telerik Fiddler er et kjempebra debugging-verktøy for webutviklere, som kan fange opp all trafikk mot nettsiden din ved å oppføre seg som en proxy. Da skal du kunne se forespørselen som sendes fra brukergrensesnittet ditt, og se hva verdien til alle form-inputs faktisk er. Jeg har en sterk mistanke om at du har en eller annen form for automatisk utfylling av secretKey-feltet, slik at denne har korrekt verdi selv om du ikke fyller det ut selv.
Takk for svar igjen Moff og for at du tar deg tid.

Vet ikke om jeg får sett sql spørringen som kjører når jeg gjør dette da utfylling av feltet er på en android app som bruker ajax og henter php scriptet fra serveren min, men skal se hva jeg får til her.
Er du sikker på at variabelen er tom til å begynne med?
@Rosander Hva tenker du her?

skey og email er 2 vanlige input felt som blir sjekket med AJAX og sendt til php scriptet som igjen poster/sletter i databasen.
Selv om dette brukes fra en android-app kan du teste med f.eks Postman for å enklere se hva serveren din returnerer.

Du kan også lage deg en simpel funksjon som logger til fil:

Kode

function log_to_file($message) {
  $log = fopen("logging.txt", "a");
  fwrite($log, time() . " - " . $message . "\n");
  fclose($log);
}

if (sql_went_wrong) {
  log_to_file('Spørringen krasjet fordi...');
}
Hei! Jeg vil gjerne slenge inn et forslag, og et spørsmål.

Forslaget er som lyder: Moff foreslo at trådstarter skulle gi brukeren "falske opplysninger" for å gjøre det enda mer sikkert, da tenker jeg på det med at brukeren skal få tilbakemelding om at oppføringen er slettet uansett, selv om den egentlig ikke fins. Mitt forslag er da at trådstarter gir brukeren en slik melding, eller lignende:
Takk! Hvis mail-adressen din finnes i databasen, så er den nå slettet.
Vis hele sitatet...
På denne måten trenger vi ikke kalle dette for en falsk opplysning, for det er jo sant.

Og så er spørsmålet til Moff:
Sitat av Moff Vis innlegg

Kode

$sql = "DELETE FROM form WHERE secretKey='$skey' AND email='$delete'";

echo $sql . '<br><br>';
Vis hele sitatet...
Hvorfor bruker du <br><br> og ikke <p>? Er det en spesiell grunn, eller var det bare en tilfeldighet?
Sist endret av Stingray; 26. april 2019 kl. 12:15.
Sitat av Stingray Vis innlegg
Hvorfor bruker du <br><br> og ikke <p>? Er det en spesiell grunn, eller var det bare en tilfeldighet?
Vis hele sitatet...
Sikkert bare pga at dette er veldig midlertidig og ikke noe som skal være der permanent. Da er det ikke så farlig hvordan markupen er.
Sitat av Nikon01 Vis innlegg
@Rosander Hva tenker du her?

skey og email er 2 vanlige input felt som blir sjekket med AJAX og sendt til php scriptet som igjen poster/sletter i databasen.
Vis hele sitatet...
Jeg tenkte at det kan være at nøkkel variabelen blir utfylt ett sted tidligere i koden slik at variabelen allerede består av den hemmelige nøkkelen.
Så problemet er ikke nødvendigvis at spørringen ikke sjekker den hemmelige nøkkelen men at nøkkelen allerede er satt.

Grunnen til at jeg mistenker dette er at du bruker vanlige variabler og $_POST variabelen om hverandre i spørringene, og da er det fort gjort at verdier blir hengende igjen.

Jeg hadde ett lignende problem for en stund siden hvor jeg måtte tømme post variablene før jeg tok de i bruk, en typisk "jeg vet ikke hvorfor, men det funker ikke uten".

Problemet viste seg å ligge tidligere i prosessen hvor en liten kode feil gjorde at feil verdier ble skrevet til post variabler.
Hei folkens. Takk for gode svar. Skal sjekke ut litt mer.

Her er da både html, ajax og PHP:

Kode

<form action="" method="post" id="delete-user">
    <label for="delete">Delete my user from the records</label> <br>
    <input type="email" name="delete" placeholder="Your email"><br>

    <label for="skey">SecretKey:</label>
    <input type="text" name="skey" placeholder="Your secret key">


    <button type="submit">Delete me!</button>
    <p id="delete-message"></p>
</form>

Kode

$("#delete-user").submit(function(e) {
    e.preventDefault();

    $.ajax({
        url: "****/delete_form_data.php",
        method: "post",
        data: $("form").serialize(),
        dataType: "text",
        success: function(strMessage) {
            $("#delete-message").text(strMessage);
            $("#delete-user")[0].reset();
        }
    });
});

Kode

$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}


$delete = $_POST['delete'];
$skey = $_POST['skey'];

$query = mysqli_query($conn, "SELECT * FROM form WHERE email='$delete'");

$sql = "DELETE FROM form WHERE secretKey='$skey' AND email='$delete'";

if(mysqli_num_rows($query) < 0) {
	echo "The email: " . $_POST['delete'] . " does not exist in our records";
}
elseif(!mysqli_query($conn, $sql)) {
	echo "Could not delete data!";
}
else {
	echo "You have deleted your information from our site. We hope you will come back!";
}


mysqli_close($conn);
Sitat av Nikon01 Vis innlegg
Hei folkens. Takk for gode svar. Skal sjekke ut litt mer.

Her er da både html, ajax og PHP:

Kode

<form action="" method="post" id="delete-user">
    <label for="delete">Delete my user from the records</label> <br>
    <input type="email" name="delete" placeholder="Your email"><br>

    <label for="skey">SecretKey:</label>
    <input type="text" name="skey" placeholder="Your secret key">


    <button type="submit">Delete me!</button>
    <p id="delete-message"></p>
</form>

Kode

$("#delete-user").submit(function(e) {
    e.preventDefault();

    $.ajax({
        url: "****/delete_form_data.php",
        method: "post",
        data: $("form").serialize(),
        dataType: "text",
        success: function(strMessage) {
            $("#delete-message").text(strMessage);
            $("#delete-user")[0].reset();
        }
    });
});

Kode

$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}


$delete = $_POST['delete'];
$skey = $_POST['skey'];

$query = mysqli_query($conn, "SELECT * FROM form WHERE email='$delete'");

$sql = "DELETE FROM form WHERE secretKey='$skey' AND email='$delete'";

if(mysqli_num_rows($query) < 0) {
	echo "The email: " . $_POST['delete'] . " does not exist in our records";
}
elseif(!mysqli_query($conn, $sql)) {
	echo "Could not delete data!";
}
else {
	echo "You have deleted your information from our site. We hope you will come back!";
}


mysqli_close($conn);
Vis hele sitatet...
Tar jeg feil når jeg mener at du faktisk ikke sjekker om secret key matcher databasen?
For dersom du ikke skriver inn noe i skjemaet vil den variabelen være tom og spørringen ser etter secret key ' ' (ingenting).

Det første du må gjøre er jo å kreve at secret key blir tastet inn i skjemaet.

Så må du sjekke at post variablene inneholder riktig data, sjekk at epost har riktig format og at secret key ikke er tom.
Så må du escape SQL som nevnt over.

Deretter bør du ha en spørring etter som returnerer row id som matcher epost og secret key, dersom den returnerer 0 treff vet du at at brukeren ikke eksisterer eller at secret key er feil (da røper du ikke om eposten eksisterer i databasen).
Så kan du da velge å slette den id-en fra databasen.

Har ikke pc tilgjengelig så får ikke skrevet om koden din.
@Yochi
Har lastet ned postman og må sjekke litt hvordan dette fungerer.
Ut ifra hva jeg kan se kan jeg her sjekke API'er samt kjøre POST / GET etc mot server.
Kjørte en post mot link til PHP scriptet nå, men kan ikke se noen feilmeldinger.
I see you...
NAPse's Avatar
Sorry, men logikken din er desverre litt på bærtur.

Kode

if(mysqli_num_rows($query) < 0) { //Denne evalueres ALLTID som false. Du vil ikke kunne få negativt antall rader fra en spørring.
	echo "The email: " . $_POST['delete'] . " does not exist in our records";
}
elseif(!mysqli_query($conn, $sql)) { //Denne evalueres som !true ved "suksessfull" spørring(som "alltid" vil skje). **
	echo "Could not delete data!";
}
else { //Denne meldinga kjører derfor uansett input. Raden blir ikke sletta, men du vil få denne meldinga som output.
	echo "You have deleted your information from our site. We hope you will come back!";
}
** Fra databasehåndteringssytemet sitt perspektiv vil en spørring som "sletter" alle rader som har skey=x og email=y anses som suksessfullt selv om det ikke fantes noen rader med disse verdiene. Moff nevte dette, men vet ikke helt om du forstod det. Bruk mysqli_affected_rows() fremfor å sjekke at mysqli_query() ikke returnerer false.

Her er et eksempel på forsøk på å slette en rad som ikke eksisterer. Som du ser returnerer mySQL en sann verdi til php scriptet ditt.

Kode

> DELETE FROM form WHERE secretKey='finnesikke' AND email='finnes@heller.ikke';
Query OK, 0 rows affected (0.00 sec)

Noe i denne duren er nok noe mer fornuftig.

Kode

if(mysqli_num_rows($query) < 1) {//Denne hører til SELECT spørringen og har "ingen ting" med neste spørring å gjøre. elseif her gir derfor ikke mening.
	echo "The email: $email does not exist in our records. <br/>";
}

mysqli_query($conn, $sql); // DELETE spørringen kjøres

if(mysqli_affected_rows($conn) > 0){ //Denne hører til DELETE spørringen.
	echo "You have deleted your information from our site. We hope you will come back!<br />";
}
else{
	echo "Could not delete data!<br />";
}
Jeg får det ikke til å henge på greip at rader slettes uten at både skey og email stemmer overens i database og spørring.
Er du helt sikker på at dette skjer? Har du gått inn i databasen og sjekket, eller har du stolt på dine egne "feilmeldinger"?
Hei NAPse.

Dette er heldigvis bare spørringer jeg satt opp for å sjekke at ting "fungerer" før jeg på en måte "sikrer" det bedre og får meldinger oppe og går.

Når det gjelder det med sletting:
Ja. Jeg er helt sikker på at databasen slettes selv om skey ikke er skrevet inn. Jeg har databasen oppe i en fane mens jeg tester samt at database entryene kommer inn i appen fortløpende når noe slettes eller legges inn.

Når jeg legger inn en entry, sjekker jeg alltid både appen og databasen har det jeg satt inn og det samme gjelder når jeg sletter. Hvorfor denne ikke "reagerer" på skey har jeg enda ikke klart å finne ut av.

Skal sette meg ned å se litt på dette om noen minutter og gjøre SQL'en bedre og se litt mer på det da. Men kan ikke se noe som er feil i selve spørringen. Men jeg lurer på om det er noe med AJAX funksjonen som ikke fungerer som den skal, MEN jeg kan jo ta litt "smør på flesk" som vi sier og gjøre dobbelsjekk om faktisk email er i tabellen og skey er i tabellen og sjekke om disse er i samme rad etc. Skal se litt på dette straks så oppdaterer jeg vell med hva jeg finner ut av / ikke finner ut av.

UPDATE:

Ting fungerer i hvert fall nå. Nå sletter den ikke raden når bare epost er lagt inn, men KUN når epost og secretKey er lagt inn og match i databasen.

Koden jeg bruker nå er som følger:

Kode

$conn = new mysqli($servername, $username, $password, $dbname);

if($conn->connect_error) {
	print_r( "Connection failed: " . $conn->connect_error );
} else {

}



$sql = "SELECT email, secretKey FROM form WHERE email='$email' AND secretKey='$skey'";


if(mysqli_num_rows($sql) < 1) {
	echo "Your information does not match our records";
}


if($stmt = $conn->prepare($sql)) {

	$email 	= $_POST['delete'];
	$skey 	= $_POST['skey'];

	$stmt->bind_param("ss", $email, $skey);
	$stmt->execute();
	if(!$stmt->errno) {

	}

	$stmt->close();

}

$email 	= $_POST['delete'];
$skey 	= $_POST['skey'];


$delete = $conn->prepare("DELETE FROM form WHERE email='$email' AND secretKey='$skey'");
$delete->bind_param("is", $email, $skey);
$delete->execute();
$delete->close();

if(mysqli_affected_rows($conn) > 0) {
	echo "You have deleted your information from our site. We hope you will come back one day!";
} else {
	echo "Could not delete data. Try again later!";
}



$conn->close();

Måtte prøve og feile 999 ganger, men til slutt fungerte dette.
Nå tenker jeg at feilmeldingen til if statementen under "SELECT" må rettes litt på. Denne kjører selv om den er vellykket eller ikke.


Noen innvendinger på hvordan koden her er satt opp? Jeg tar gjerne imot mange tips for å kunne bli bedre i det jeg gjør her.


Ha en fin helg alle sammen og takk til alle som har svart her.
Sist endret av Nikon01; 26. april 2019 kl. 22:45. Grunn: Automatisk sammenslåing med etterfølgende innlegg.
Hei,

Anbefaler at du setter deg inn i SQL Injection fra ett sikkerhetsperspektiv, fikk mange aha-opplevelser på Ethical Hacker kurs. Du kan eksempelvis lese her som en start: https://www.w3schools.com/sql/sql_injection.asp

Selv gikk jeg vekk fra queries i web-kode og over til stored procedures:

http://www.mysqltutorial.org/introdu...rocedures.aspx
Prepared statements, som ble nevnt litt over her.
https://www.w3schools.com/php/php_my...statements.asp
Sitat av Superspurv Vis innlegg
Prepared statements, som ble nevnt litt over her.
https://www.w3schools.com/php/php_my...statements.asp
Vis hele sitatet...

Hei superspurv.

Har satt opp preparet statement som vist i scriptet over her. Trenger bare å gjøre det på insert og fetch scriptene mine også gå igjennom litt annet.
Sitat av Nikon01 Vis innlegg
Noen innvendinger på hvordan koden her er satt opp? Jeg tar gjerne imot mange tips for å kunne bli bedre i det jeg gjør her.
Vis hele sitatet...
Du må gjøre om litt på parameter

Kode

$delete = $conn->prepare("DELETE FROM form WHERE email='$email' AND secretKey='$skey'"); // Variablene settes direkte.
$delete->bind_param("is", $email, $skey);
Må bli til:

Kode

$delete = $conn->prepare("DELETE FROM form WHERE email=? AND secretKey=?"); // Variablene settes via prepared statements
$delete->bind_param("ss", $email, $skey); // E-post er nok ikke en integer, så her må du ha ss (string, string).
Ellers ville jeg droppet SELECT-spørringen og bare prøvd å slette. Får du 0 resultater på DELETE har du funnet ut det samme som SELECT-spørringen gir deg.
Takk @Yochi .

Skal ta å sjekke ut dette.

God dag igjen folkens.

Ble så gode svar sist gang at jeg nesten bare må fortsette. For her var det mye god kunnskap og mye å lære.
Oppsummert så fungerer alt som det skal nå. Insert, delete og hente ut brukere går helt fint og alle feilmeldinger er korrekte.

Nå tenkte jeg å bevege meg et steg videre for å se om det går an å gjøre ting kanskje "LITT" enklere enn hva jeg har gjort nå.



Det jeg nå skal er å ha et registreringsskjema. Dette ser da sånn her ut i PHP / SQL:

Kode

$username 	= $_POST['username'];
$password 	= $_POST['password'];
$email 		= $_POST['email'];
$country 	= $_POST['country'];


$hashed_password = $password_hash($password, PASSWORD_DEFAULT);



if(!preg_match("/^([a-zA-Z' ]+)$/",$username)) {
    die ("invalid username");
}

if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
	die("Invalid email format");
}


$sql = "INSERT INTO login (username, password, email, country) VALUES ('$username', '$hashed_password', '$email', '$country')";


if($stmt = $conn->prepare($sql)) {

	$stmt->bind_param("ssss", $username, $password, $email, $country);
	$stmt->execute();
	if(!$stmt->errno) {

	}

	$stmt->close();

}
Dette er første utkast og vil bli forbedret etter at jeg vet at funksjonaliteten fungerer og brukere kommer inn i databasen (Ikke testet enda).
Men så kommer jeg til der man skal logge inn. Jeg har aldri brukt password hash med php før. Har vell kanskje bruke MD5 og SHA1 kryptering en gang i sikkert 2010, 2011 en gang, men dette kan jeg ikke huske.


Når passordet er kryptert nå så ligger det kryptert passord i Passord entryen i databasen.
Hvordan kan jeg her da verifisere dette passordet ved login?
Må det da her blir 2 SELECT spørringer? En mot hashed password og 1 mot username?

Tenker noe ala dette? (Ikke fullført enda OG heller ikke lagt inn prepared her, men dette blir).

Kode

$username 	= $_POST['username'];
$password 	= $_POST['password'];



$hashed_password = $password_hash($password, PASSWORD_DEFAULT);

$fpass = "SELECT password FROM login WHERE password='$hashed_password'";




if(!preg_match("/^([a-zA-Z' ]+)$/",$username)) {
    die ("invalid username");
}

if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
	die("Invalid email format");
}

$sql = "SELECT username FROM login where username='$username'";
Er det noe ala dette man skal gjøre for å verifisere passord og brukernavn eller har dere kanskje noen bedre ideer?
Sist endret av Nikon01; 29. april 2019 kl. 10:47. Grunn: Automatisk sammenslåing med etterfølgende innlegg.
Hei! Det er en liten stund siden jeg holdt på med dette, men: md5 bør du ikke bruke! Det ville ikke jeg gjort ihvertfall. sha1 er det du bør bruke, om ikke noen andre har noe bedre, så klart. Det går jo også an å kombinere flere krypteringer sammen.

Og så må jeg skyte inn: Hvorfor ikke kjøre spørring der du både sjekker brukernavn og passord samtidig i en spørring, istedenfor å dele de opp? (Select username, password from .. where username =... and password=...) Det du gjør nå, er etter min oppfatning, å sjekke om passordet eksistrerer i databasen, men ikke brukernavn?
Hei Stingray. Takk for svar.
Nei MD5 husker jeg at man ikke bør bruke.
Men søker jeg på google etter PHP kryptering av passord er det bare password_hash funksjonen jeg får opp. Dette er vell ikke MD5? Syns jeg husker at MD5 var en egen funksjon(MD5()) etc.


Hvorfor det deles opp her er jeg usikker på. Har sett litt på forskjellige guider og "best practices" og her ser jeg de først sjekker passord for å verifisere passord, så sjekker de brukernavn og hvis de begge er korrekt, så får de logget inn.

Jeg tenkte selv på bare å kjøre 1 SQL spørring, men da ble jeg usikker på hvordan jeg kan kjøre en verifisering av passordet.
Php gjør passord hashing veldig enkelt. for å verifisere det hashede passordet benytter man seg av password_verify

1. hent hashet passord til brukeren fra db.
2.

Kode

<?php
if (password_verify($password, $hash)) {
    // Success!
}
else {
    // Invalid credentials
}
Hei patrick.

Nå har jeg lagt inn password_hash og laget en bruker som ligger i databasen.

Når jeg nå kjører

Kode

if(password_verify($password, $fpass)) {
	var_dump("Correct Password");
} else {
	var_dump("Not the correct password");
}
der $fpass er som følger:

Kode

$fpass = "SELECT password FROM login WHERE password='$password'";

Så kjører else statementen.

I login, skal jeg sjekke passordet de skriver inn, f.eks '123456' mot $fpass eller skal dette krypteres på noen måte ?
Sist endret av Nikon01; 29. april 2019 kl. 12:07.
Du lagrer hashed versjon av passordet i databasen. Når brukeren logger inn må du hashe passordet (input) før du sammenligner.

Trenger forresten ikke hente passordhashen fra databasen.

Kode

$input_password = password_hash($_POST['password'], PASSWORD_DEFAULT);
$input_username = $_POST['username'];

if($stmt = $conn->prepare('SELECT * FROM login WHERE username=? AND password=?')) {
  $stmt->bind_param("ss", $input_username, $input_password);
  // Sjekk hva som returneres og fortsett...
}
Brukeren må ha både brukernavn og passord riktig for å logge inn så bare sjekk begge deler i en spørring.
Hei igjen!
md5 er egen funksjon, ja
Eksempel:

Kode

$password = ‘hei’;
$hashed = md5($password);
Samme greia med sha1, bare bytt ut md5 med sha1 i koden ovenfor

Som sagt en stund siden jeg holdt på med dette, så ikke skyt meg i hodet, men i foten, hvis behov. Skriver også fra iPhone, så anførselstegn ble vanskelig å skrive, det ser nemlig slik ut: «» på iPhone

Og så reagerer jeg fortsatt på at passordet blir sjekket først, men ikke brukernavnet. Hva om noen av all tilfeldighet har det samme passordet? Jeg vet ikke jeg nei, noen andre vet mer enn meg om dette, det er sikkert. Fint om jeg får en kort forklaring

PHP har åpenbart utviklet seg siden jeg kodet sist.
Takk for svar begge 2.

@Yochi :
Hvorfor bruker man her ? ved username og password i querryen?
Hvis jeg nå kjører password_verify, hvordan henter jeg ut hashen fra databasen for å sammenligne? Regner med jeg må dette?

For å sjekke brukernavn og passord så kjører jeg 2 IF statements da? Først sjekker jeg om passord stemmer også hvis det gjør det kan jeg kjøre en IF inni der om at hvis brukernavnet stemmer så sendes personen videre?

Setter pris på et eksempel da PHP har forandret seg en del siden jeg holdt på med dette sist.


@Stingray :
Ja MD5 husker jeg godt "Back in the days".

Bruker som regel ALDRI ren PHP selv for tiden da vi bygger på Wordpress, og dette blir da litt anderledes å slipper å tenke på sånt. Men ønsker gjerne å lære ren PHP for videre utvikling selv
Sitat av Nikon01 Vis innlegg
Takk for svar begge 2.

@Yochi :
Hvorfor bruker man her ? ved username og password i querryen?
Hvis jeg nå kjører password_verify, hvordan henter jeg ut hashen fra databasen for å sammenligne? Regner med jeg må dette?

For å sjekke brukernavn og passord så kjører jeg 2 IF statements da? Først sjekker jeg om passord stemmer også hvis det gjør det kan jeg kjøre en IF inni der om at hvis brukernavnet stemmer så sendes personen videre?

Setter pris på et eksempel da PHP har forandret seg en del siden jeg holdt på med dette sist.


@Stingray :
Ja MD5 husker jeg godt "Back in the days".

Bruker som regel ALDRI ren PHP selv for tiden da vi bygger på Wordpress, og dette blir da litt anderledes å slipper å tenke på sånt. Men ønsker gjerne å lære ren PHP for videre utvikling selv
Vis hele sitatet...
Spørsmålstegnene er grunnen til at du bruker "$stmt->bind_param()", det er der input faktisk mappes til SQL-spørringen din for å gjøre det sikrere i forhold til SQL-injections. Vil anbefale deg å se litt mer på denne siden.

Så lenge du hasher brukerinput på samme måte som du hasher passordet når det lagres til databasen vil jeg tro det holder å sammenligne hash med hash. Så da trenger du egentlig bare SQL-spørringen der du sjekker om "brukernavn == brukernavn" og "input-passord-hash == lagret-passordhash".

Du trenger ikke 2 IF-setninger. <SELECT * FROM login WHERE username="yochi" AND password="hashen-av-passordet-mitt"> vil bare returnere en rad om begge disse sjekkene stemmer. Så da har du allerede kontrollert min input mot det du har lagret.
@Yochi Ok.

Skal sjekke litt ut på w3.


Nå har jo jeg satt dette opp sånn som det her for øyeblikket:

Kode

$sql = "SELECT username, password FROM login WHERE username=? AND password=?";

if($stmt = $conn->prepare($sql)) {

	$username 	= 'joakim';
	$password 	= '123456';

	$input_password = password_hash($password, PASSWORD_DEFAULT);



	$stmt->bind_param("ss", $username, $input_password);
	$stmt->execute();


	if(password_verify($input_password, ?)) {
		var_dump("Match");
	} else {
		var_dump("No match");
	}


	$stmt->close();
	$conn->close();


}
Hva skal inn her på if(password_verify($input_password, ?) ?
Må vell hente ut raden password for å kunne sammenligne?


En annen ting.

Hvis jeg sjekker $input_password så gir jo denne en ny hash hver gang. Hvordan kan denne da sammenlignes med den i databasen? Er det sånn at password_verify decrypterer passordet og sammenligner den decrypterte stringen?
Beklager, jeg har sagt litt feil rundt password_hash og password_verify ser jeg. Du må sammenligne passord i klartekst med hash lagd av password_hash for å finne ut om passordene matcher.

Så da ville jeg gjort det sånn her:

Kode

$sql = "SELECT username, password FROM login WHERE username=?";
     
if ($stmt = $conn->prepare($sql)) {
      
  $username 	= $_POST['username'];
  $password 	= $_POST['password'];
      
  $stmt->bind_param("s", $username);
  $stmt->execute();
 
  $result = $stmt->get_result();

  if ($result->num_rows > 0) {
    $row = $result->fetch_array(MYSQLI_NUM);
    $db_hash = $row[0]['password'];

    if (password_verify($password, $db_hash)) {
      // Username og passord matchet, så denne brukeren kan logge inn.
    }
    else {
      // Passordet er geil.
    }
  }
  else {
    // Ingen resultater, ukjent brukernavn.
  }

  $stmt->close();
  $conn->close();
}
Med forbehold om at MySQLi-koden ikke stemmer 100%.
@Yochi
Takk.
Da skal jeg sjekke ut dette.

@Yochi
Da har jeg sjekket litt og prøvd å finne en løsning her. Ikke kommet noen vei enda.
Jeg har gjort en lignende måte som dette tidligere men endt opp med "500 Internal Server Error" og denne meldingen får jeg nå også.

Hvis jeg tar bort kode fra $result og ned til slutten på IF statementen vil jeg kunne se en var_dump.
Så en plass inni her ligger det noe jeg ikke helt skjønner kan forårsake en 500 error.

Kode

$sql = "SELECT username, password FROM login WHERE username=?";

if($stmt = $conn->prepare($sql)) {

	$username 	= 'nikon';
	$password 	= '123456';

	$input_password = password_hash($password, PASSWORD_DEFAULT);


	$stmt->bind_param("s", $username);
	$stmt->execute();

	$result = $stmt->get_result();

	if ($result->num_rows > 0) {
	  $row = $result->fetch_array(MYSQLI_NUM);
	  $db_hash = $row[0]['password'];

	  if (password_verify($password, $db_hash)) {
	    var_dump("Match");
	  }
	  else {
	    var_dump("No match");
	  }
	}
	else {
	  var_dump("That username does not exist!");
	}


}


UPDATE:
Har søkt en del rundt nå og det ser ut som jeg ikke kan bruke get_result() funksjonen.
Flere skriver at denne fungerer kun når man har MySQL native driver installert (?) og dette virker veldig basic at egentlig bør være installert på servere når man hoster.

Men det er uansett denne funksjonen som gir meg "500 Internal Server Error", så da må jeg nesten finne en annen måte man kan få dette til å fungere på.
Sist endret av Nikon01; 29. april 2019 kl. 17:34. Grunn: Automatisk sammenslåing med etterfølgende innlegg.
Limited edition
Moff's Avatar
Starter med svar til Stingray:

Sitat av Stingray Vis innlegg
Og så er spørsmålet til Moff:

Hvorfor bruker du <br><br> og ikke <p>? Er det en spesiell grunn, eller var det bare en tilfeldighet?
Vis hele sitatet...
Det er litt hipp som happ, men jeg bruker <br> bevisst for denne typen output. <p> er en avsnitt-blokk, som egentlig ikke handler om å lage linjeskift eller marginer - det er for å dele opp tekstblokker. <br> er linjeskift-kode, så når jeg vil ha linjeskift bruker jeg alltid <br>. I de fleste av mine nettsider vil jeg også ha CSS-kode knyttet til <p>-elementet som kan være uheldig, eller i verste fall føre til at jeg ikke får noe mellomrom i det hele tatt.

Sitat av Stingray Vis innlegg
sha1 er det du bør bruke, om ikke noen andre har noe bedre, så klart. Det går jo også an å kombinere flere krypteringer sammen.
Vis hele sitatet...
SHA-1 er minst like deprecated som det MD5 er. For PHP i 2019 så er det Argon2 eller Blowfish som gjelder. Argon2 er den nyeste algoritmen og det er denne man bør bruke hvis den er tilgjengelig. I de fleste PHP-installasjoner akkurat nå, så er det imidlertid Blowfish som er det beste alternativet.

Når man bruker password_hash(), så kan du bestemme hvilken algoritme som skal brukes. I kodeeksemplene deres ovenfor så skriver dere kun PASSWORD_DEFAULT, som lar PHP selv velge hvilken algoritme som skal benyttes. Hvis du vil ha kontroll på dette selv, så kan du skrive enten PASSWORD_BCRYPT (for å bruke Blowfish) eller PASSWORD_ARGON2I for å bruke Argon2. Det er også en nyere versjon av Argon2 tilgjengelig, med konstanten PASSWORD_ARGON2ID. Men som sagt, PHP-installasjonen din må være kompilert med støtte for Argon2 for at denne algoritmen skal være tilgjengelig. I skrivende stund så er Blowfish også ansett for å være trygt nok.

MD5 og SHA-1 skal imidlertid ikke benyttes, de regnes som utrygge. Årsaken til at det er sånn handler om hvordan hashing fungerer, så la oss ta et lite krasjkurs i nettopp det.

(Og denne delen er høyst relevant for trådstarter også).

Først og fremst; hashing er ikke det samme som kryptering. Når vi snakker om kryptering, så tenker vi på innhold som er maskert på en måte som gjør det svært vanskelig eller umulig å tyde - men innhold som er kryptert kan de-krypteres for å bli leselig igjen. Innhold som er hashet kan aldri "de-hashes". Jeg pleier å forklare dette med analogien om et papirark. Når du hasher noe så er det som om du krøller sammen et papir. Du kan aldri anti-krølle det; det er permanent ødelagt. Samme hvor mye du prøver, så vil papiret alltid være krøllet. Hele trikset med en hashing-algoritme, være seg MD5, SHA-1, Blowfish eller Argon2, er at det "krøller papiret" på nøyaktig samme måte, hver eneste gang.

Hvis du skriver inn samme passord, bruker samme hash-algoritme, så skal du alltid få nøyaktig samme hash-resultat. Hver gang. Og nå hører jeg trådstarter si "jammen, når jeg bruker password_hash() så får jeg ulike hasher med samme passord!!". Ja, det gjør du nok - men det handler ikke om selve hashing-algoritmen, det handler om et annet sikkerhetstiltak som vi kaller salting.

Vitsen med hashing er at vi som webmastere skal kunne verifisere brukernes passord uten at vi på noe tidspunkt trenger å vite hva passordet deres egentlig er. Vi får passordet inn, hasher det, og så sjekker vi om hashen vi fikk matcher den hashen vi har lagret fra før. Hvis begge er like, så vet vi at brukeren har tastet inn korrekt passord. Vi vet ikke hva passordet er, men vi vet at det er riktig.

La oss nå si at en hacker får tilgang til databasen din, og stjeler alle brukerne dine. Hackeren kan ikke se hva passordene til noen er, fordi de har bare en haug med ubrukelige hasher. Det hackeren imidlertid kan gjøre er å lage et skript som genererer hasher for alle mulige passord, og deretter sammenligne hashene fra databasen din og se om de tilfeldigvis finner en match. Det er jo ganske enkelt å gjøre, og det er her salting kommer inn i bildet. Salting er en teknikk hvor du tilsetter et "salt" i brukernes passord, som er en tilfeldig generert string. Hvis passordet mitt er "asdf", så kan du tilsette saltet "1234", sånn at passordet mitt blir "asdf1234". Hashen for mitt passord vil da ikke lengre matche "asdf", den matcher kun "asdf1234". Hverken du, jeg eller hackeren vet hva saltet er for noe, det er generert i PHP-koden et sted, urørt av menneskehender. Og selv om noen vet hva saltet er, så vet man ikke hvordan det blir implementert. Kanskje det er "asdf1234", kanskje det er "1234asdf", eller noe mer komplisert. Poenget er i alle fall at en hacker ikke vil klare å finne ut hva passordet er, fordi de ikke finner noen match mot hashen som gir mening.

Dette er grunnen til at du tilsynelatende får ulike resultater fra password_hash(); den salter automatisk. Resultatet av hashing-algoritmen vil faktisk inneholde informasjon om hvilken algoritme som er brukt, og hva saltet er. Det er ikke lett for oss mennesker å se det, men hvis du har drevet mye med hashing, så vil du kunne kjenne igjen algoritmen bare ved å se på prefiksen til hashen du får. Blowfish er veldig lett å kjenne igjen, fordi hashen starter med $2y$-prefiksen. Dette må til for at password_verify skal kunne skjønne hvordan sammenligning av hash og passord skal foregå, sånn at den bruker korrekt algoritme og korrekt salt. Hvis den ikke gjør det, så vil du jo som du har merket, få problemer med å sammenligne hashene selv om passordet er korrekt.

Bonusinfo: Hvorfor er MD5 og SHA-1 ikke lengre anbefalt, hvis de i prinsippet gjør akkurat det samme som Blowfish og Argon2? Enkelt og greit: Kollisjoner. En MD5- og SHA-1-hash har en fast lengde på X antall tegn. Derfor vet vi at flere ulike passord nødvendigvis MÅ resultere i nøyaktig samme hash, fordi det finnes uendelig mange unike passord i verden, men fordi hashen har en fast lengde, så er det et begrenset antall unike hasher. Etter hvert som datamaskinene våre ble raskere og raskere, så ble det lettere og lettere å generere oppslagsverk for alle mulige MD5- og SHA-1-hasher, og dermed tilsvarende lett å knekke dem. Blowfish og Argon2 er mye mer kompliserte, og er derfor langt mindre sårbare for denne typen angrep. De er ikke immune mot det, men det er trygge "nok". Inntil videre. MD5 og SHA-1 hadde heller ikke noen integrert salting-funksjon, så vi utviklere måtte implementere dette selv. Det slipper vi med de moderne algoritmene.

Det er 3 PHP-funksjoner vi må ha oversikt over:
password_hash() - Genererer hashen med valgt algoritme og automatisk salting. Brukes når passordet lagres eller må re-hashes.
password_verify() - Sammenligner et passord fra brukeren med en hash du har lagret. Brukes når man logger inn.
password_needs_rehash() - Kan alternativt brukes for å sjekke om et passord bør re-hashes, når en ny og bedre hash-algoritme har blitt tilgjengelig. Dette er sjeldne greier, men lurt å implementere hvis du bruker PASSWORD_DEFAULT (som automatisk velger den beste algoritmen). Hvis du da har brukt Blowfish, og Argon2 plutselig blir tilgjengelig for deg, så kan du automatisk oppgradere hashen til brukerne dine neste gang de logger på.

Jeg har nevnt PDO en gang tidligere her, og jeg er litt på nippet til å anbefale deg å skifte fra MySQLi til PDO. Det er fordeler og ulemper med begge to, men min profesjonelle mening er at PDO er det bedre valget. Én ting som sikkert blir tydelig for deg allerede er at prepared statements i MySQLi er et salig rot.

Kode

$sql = "SELECT id FROM login WHERE username = ? AND password = ?";
Prepared statements må du bruke for å sikre deg mot SQL-injections, slik at du skriver spørringen først med ? som placeholder for variabler, og deretter bruker du bind_param() for å erstatte placeholderne (?) med de verdiene du skal ha. Bare for å ha nevnt det; du bruker kun prepared statements når du har input fra en bruker som du ikke vet hva er; du trenger selvsagt ikke bruke prepared statements hvis du har en spørringen uten variabler, eller med variabler som du er 110% sikker på at er trygge å bruke. Prepared statements bruker bittelitt lengre tid på å kjøre enn rene spørringer, så derfor bør du ikke bruke det mer enn du må.

I PDO så er systemet ganske likt, men du har tilgang til named parameters i prepared statements. Spørringene mine ser derfor slike ut:

Kode

$sql = "SELECT id FROM login WHERE username = :username AND password = :password";
Forskjellen er at jeg kan skrive hva placeholderne skal hete, og det gjør koden MYE mer oversiktelig. I PDO trenger du heller ikke dille med datatyper; det håndteres automatisk.

MySQLi:

Kode

$stmt->bind_param("ss", $input_username, $input_password);
PDO:

Kode

$stmt->bind_param(":username", $input_username);
$stmt->bind_param(":password", $input_password);
Ikke noe "ss" for "string, string". Bare plopp inn variablene og kjør på. Du kan også binde i en hvilken som helst rekkefølge, fordi du har tilgang til named parameters. I kompliserte spørringer blir det ofte tullball når du må holde orden på rekkefølgen på parameterne.

Du er sikkert ikke så interessert i å skrive om alt sammen til PDO nå, men det er i alle fall noe å tenke over til senere. PDO har andre fordeler også, men det kan vi ev. ta ved en senere anledning.

[quote=Nikon01;3504305]@Yochi
Så en plass inni her ligger det noe jeg ikke helt skjønner kan forårsake en 500 error.
[/code]

HTTP 500 kommer som regel hvis du har en syntaksfeil i koden eller det genereres en exception som du ikke håndterer med try/catch. Typisk så betyr det mangel på en ; et sted i koden, eller at du har en syntaksfeil i en SQL-spørring.

På de fleste servere har du mulighet til å skru på output for feilmeldinger med disse linjene i starten av koden din:

Kode

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
Dette skal du ikke gjøre i et produksjonsmiljø (ting som er åpen på internett), fordi feilmeldingene kan inneholde sensitiv informasjon som hackere ikke bør få tak i. Filnavn, spørringer - slike ting.

Det kan være greit for å debugge, siden du ser nøyaktig hvor problemet oppstår.

Som en kommentar til den siste koden du postet; jeg pleier aldri å bry meg om å sjekke om prepare() gikk bra. Min antakelse er at prepare() alltid går bra så lenge SQL-en er gyldig (ingen syntaksfeil) og forbindelsen til databasen er OK. Hvis du får til å koble til databasen, så vil prepare() gå bra i 99,99% av alle tilfeller. Jeg tenker nå på at du skriver en if() rundt prepare() - det virker litt redundant for meg. Hver sin smak, altså - feilhåndtering er vel og bra, men det kan bli for mye av det gode også.

Fordi vi ikke liker å gi løsninger direkte, så har jeg laget et eksempel på login med PDO i stedet for MySQLi:

Kode

<?php

// Database-konfig
$dbHost = 'localhost';
$dbDatabase = 'minDatabase';
$dbUsername = 'mittBrukernavn';
$dbPassword = 'mittPassord';

// Koble til med PDO, sett charset til UTF-8 (fordi vi liker UTF-8)
try {
	$db = new PDO("mysql:host=$dbHost; charset=utf8; dbname=$dbDatabase", $dbUsername, $dbPassword, array(
		PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Sett error-mode til exception
		PDO::ATTR_EMULATE_PREPARES => false // Ikke bruk emulate prepares (få/ingen systemer gjør dette lengre, men det er greit å si ifra om det likevel)
	));
} catch(PDOException $e) {
	// Klarte ikke koble til, feilmelding tilgjengelig i $e->getMessage()
	echo 'Whops!';
	die(); // Stopp skriptet - ikke helt stuerent å gjøre dette, men det går an hvis man vil
}

if(!isset($_POST['username']) || !isset($_POST['password'])) {
	// Mangler brukernavn og/eller passord
	http_response_code(400); // Send HTTP 400 Bad request
	echo 'Ikke riktig brukernavn og/eller passord!';
	die();
}

// Hent ut passord-hashen for å sammenligne
$query = $db->prepare('SELECT id, username, password FROM users WHERE username LIKE :username'); // Prepare statement
$query->bindParam(':username', $_POST['username']); // Putt inn variabler
$query->execute(); // Utfør spørring mot databasen

// Du kan nå sjekke row count med $query->rowCount();

$row = $query->fetch(PDO::FETCH_ASSOC); // Hent første rad som et "associative array" (assoc)

if(!$row || strtolower($row['username']) != strtolower($_POST['username']) || !password_verify($_POST['password'], $row['password'])) {
	// Feil brukernavn eller feil passord
	http_response_code(401); // Send HTTP 401 Forbidden
	echo 'Ikke riktig brukernavn og/eller passord!';
	die();
}

$_SESSION['userId'] = $row['id']; // Brukeren er verifisert, logg på!
echo 'OK';

?>
Jeg foretrekker å skrive objektorientert i PHP, så jeg liker ikke å bruke die() for å stoppe skriptet midt i. Jeg pleier heller å designe koden sånn at ting flyter uten die(). Smak og behag - har ikke så fryktelig mye å si. Annet enn å øke lesbarheten.

Legg merke til hvordan jeg henter brukernavnet; jeg hater nettsider som er case-sensitive på sjekk av brukernavn! Brukernavnet mitt har stor forbokstav, men jeg vil kunne logge inn med brukernavnet i små bokstaver. Det er en filledetalj, men det er viktig for meg. LIKE er i utgangspunktet ment for søk med wildcards, men du kan også bruke den for å gjøre case-insensitive sammenligninger. Jeg vet rett og slett ikke hvorvidt LIKE er 100% nøyaktig med alle typer tegn, derfor har jeg en ekstra sjekk i koden hvor vi sammenligner $_POST['username'] med $row['username'], etter å ha brukt strtolower() på begge to (konvertert til lower case).

Du bør også sette opp brukertabellen din med en UNIQUE-key i username-kolonnen, slik at du er 100% sikker på at ingen brukere kan ha samme brukernavn.

Helt til slutt, så er det korrekt som du sier at get_result() kun er tilgjengelig med native driver. Det er pussig hvis serveren din ikke har det installert; det kan du kanskje fikse via cPanel eller tilsvarende programvare. Hvis du har SSH-tilgang med sudo kan du også installere det selv der.
Wow Moff.

Veldig langt og utfyllende svar.
At det nå har kommet noe annet enn sha1 som er bedre gikk meg hus forbi. (Sier vell sitt om hvor lenge siden jeg har holdt på med php).

Jeg har faktisk vært inne på tanken om å gjøre alt om til PDO, da PDO var det siste jeg holdt på med i PHP og syns det var mer ryddig.
Alt fungerer hos meg nå. Det eneste jeg trenger å tenke på nå er egentlig bare å få rydda koden, kanskje gjøre om til PDO og gjøre dette mer sikkert.

Dette skal kun brukes igjennom en Android applikasjon som JEG skal bruke og ingen andre, så dette er kun for treningen sin skyld for å kunne få bedre kunnskap og kunne videreutvikle og være mer behjelpelig i enkelte prosjekter på jobb.

Men takk til alle som har svart. Dette var fantastisk, og freak stiller opp som vanlig.
Ha en fin uke folkens og kos dere med en fridag i morgen (De som har det).
God kveld alle sammen.


Har nå fått både login og registrering til å samkjøre med ajax og jeg registrerer fint en bruker og logger inn med riktig info.
Mer skal gjøres og koden skal ryddes, men akkurat nå fokuserer jeg på hva som vises på skjermen og at alt fungerer fint før jeg rydder og øker sikkerhet.

Kode

$username 	= $_POST['username'];
$password 	= $_POST['password'];
$email 		= $_POST['email'];
$country 	= $_POST['country'];


$hashed_password = password_hash($password, PASSWORD_DEFAULT);





$sql = mysqli_query($conn, "INSERT INTO login (username, password, email, country) VALUES ('$username', '$hashed_password', '$email', '$country')");


if($stmt = $conn->prepare($sql)) {

	$stmt->bind_param("ss", $username, $email);
	$stmt->execute();
	if(!$stmt->errno) {

	}

}

if(!preg_match("/^([a-zA-Z' ]+)$/",$username)) {
    die ("invalid username");
}

if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
	die("Invalid email format");
}

if(mysqli_num_rows($sql) > 0) {
	echo 1;
} elseif(!mysqli_query($conn, $sql)) {
	echo 2;
} else {
	echo 3;
}


$stmt->close();
$conn->close();

Over kan dere se koden jeg kjører for øyeblikket. Data blir satt inn i databasen uten problemer der.
Problemet jeg møter her er at ALT fungerer, MEN jeg får ingen echo tilbake til AJAX. jeg har konsoll logget responsen i AJAX og får tilbake en 500 Internal Server Error. Men hvorfor får jeg en 500 error når alt fungerer?

Skulle gjerne ha hatt den echoen som kommer ut sånn at AJAX kan kjøre den responsen den skal frontent for brukeren.
Noen som har litt mer peiling på PHP enn meg som kanskje er våken nå?
Prøv med henholdsvis echo "1", echo "2" og echo "3", mest for å ha prøvd det.
Jeg tipper preg_match eller filter_var feiler. Disse blir kjørt _etter_ du har skrevet til databasen, så i grunn helt ubrukelige der de er nå!

Du har heller ikke gjort som alle andre over her har skrevet heller såvidt jeg kan se?
ALLE variablene i $sql, burde være '?', så må du kjøre en bind på de (hvis jeg skjønner folka over her riktig).
Sist endret av pinkrabbitz; 1. mai 2019 kl. 23:41.
Limited edition
Moff's Avatar
TS skriver jo at han eller hun ønsker å få system opp å kjøre før de implementerer sikkerhetstiltakene vi har foreslått.

Feilen ligger på linje 13. Du bruker mysql_query() med spørringen din i, og dette vil kjøre spørringen og returnere resultatet til $sql. Du behandler deretter $sql som om det er en string med en SQL-spørring i, ved å mate den inn i prepare(). Det er der det feiler, for $sql er resultatet av spørringen du allerede har kjørt, ikke en spørring i string-format.

Parameter-count i bind_param() er også feil, du har 4 parametre, men binder kun 2.

Du trenger altså å gjøre disse endringene:

Kode

// Linje 13:
$sql = 'INSERT INTO login (username, password, email, country) VALUES (?, ?, ?, ?)'; // <- Nå begynner du sikkert å se hvorfor jeg anbefaler PDO med named parameters her...

// Linje 18:
$stmt->bind_param('ssss', $username, $hashed_password, $email, $country);
Som pinkrabbitz skriver, så har du også validering av e-post og brukernavn etter at spørringen kjører, så det har ingen effekt. Jeg ville også implementert vask av $country, så du vet sånn ca. hva slags data du får inn der.
Sist endret av Moff; 2. mai 2019 kl. 00:36.
@Moff Tusen takk for svar igjen.
Skal gjøre om litt her og se hva jeg får. Må bare få installert nye oppdateringen på tlf for å kjøre applikasjonen.

Når du mener vask av country, tenker du da på trim(), htmlspecialchars(), strip_tags() etc?

Jeg har trim i AJAX hvis det hjelper i variablene eller bør man gjøre det i PHP i tillegg?

Nå gjelder det egentlig å skjønne seg på echo meldingene her så kan man endelig begynne å rense koden litt å implementere sikkerhetsmekanismer.

Ser nå litt på koden min og har da liksom dette:

Kode

if(mysqli_num_rows($sql) > 0) {

		$row = mysqli_fetch_assoc($sql);
		if($username == $row['username']) {
			echo 1;
		} elseif($email == $row['email']) {
			echo 2;
		}
	}  else {
		echo 3;
	}
Jeg vil jo her ha 1 echo for at brukernavn eksistere, epost eksisterer og at noe feil har skjedd, f.eks tilkobling til databasen og en echo som sier at alt gikk bra.
Det denne gjør er å echo 3 uansett om jeg tar samme brukernavn, samme epost eller om alt går igjennom.

Regner jo med at denne if statementen her kanskje skal kjøres før $stmt->execute() kjører. Har prøvd både før og etter, men fortsatt bare echo 3.
Kjenner jeg må slutte å ha 4-6 års opphold fra et språk, for da glemmer man det bare
Sist endret av Nikon01; 2. mai 2019 kl. 17:11. Grunn: Automatisk sammenslåing med etterfølgende innlegg.
Sitat av Nikon01 Vis innlegg
Jeg har trim i AJAX hvis det hjelper i variablene eller bør man gjøre det i PHP i tillegg?
Vis hele sitatet...
Alt som gjøres på klient-siden kan du gå ut fra ikke er sikkert. All input må valideres på server før du lagrer noe (på server har du kontroll, klient har du ikke kontroll over).

Sitat av Nikon01 Vis innlegg
Nå gjelder det egentlig å skjønne seg på echo meldingene her så kan man endelig begynne å rense koden litt å implementere sikkerhetsmekanismer.

Ser nå litt på koden min og har da liksom dette:

Kode

if(mysqli_num_rows($sql) > 0) {

		$row = mysqli_fetch_assoc($sql);
		if($username == $row['username']) {
			echo 1;
		} elseif($email == $row['email']) {
			echo 2;
		}
	}  else {
		echo 3;
	}
Jeg vil jo her ha 1 echo for at brukernavn eksistere, epost eksisterer og at noe feil har skjedd, f.eks tilkobling til databasen og en echo som sier at alt gikk bra.
Det denne gjør er å echo 3 uansett om jeg tar samme brukernavn, samme epost eller om alt går igjennom.

Regner jo med at denne if statementen her kanskje skal kjøres før $stmt->execute() kjører. Har prøvd både før og etter, men fortsatt bare echo 3.
Kjenner jeg må slutte å ha 4-6 års opphold fra et språk, for da glemmer man det bare
Vis hele sitatet...
Det virker mer fornuftig å sjekke om e-post/brukernavn finnes før du kjører insert-setningen. Kan være greit å lage deg noen hjelpefunksjoner for ting du gjør ofte:
  • load_user_by_mail (kan brukes for å sjekke om en bruker finnes)
  • load_user_by_username (kan brukes for å sjekke om en bruker finnes)
  • delete_user

Om du ønsker å returnere JSON kan du gjøre sånn her:

Kode

header('Content-Type: application/json');
echo json_encode(["message": "Brukernavn finnes"]);
Hei Yochi.

Jeg har f.eks sjekk av korrekt bruk av brukernavn og Epost før SQL og dette fungerer fint.
Det er kun if statementen som ikke fungerer helt som jeg vil for øyeblikket.
Har gått litt rundt på google for å sjekke hvordan man setter opp dette og jeg prøver forskjellige metoder og tester litt selv, men ender alltid opp med echo 3 uansett.
Limited edition
Moff's Avatar
Grunnen til at du alltid ender opp med "3" er at mysqli_num_rows($sql) alltid er 0. Grunnen til at den alltid er 0 er at du ikke har noen rader i $sql, fordi $sql er resultatet av en INSERT, ikke en SELECT. Den lagrer altså data, den henter det ikke. Det du har lyst å sjekke er affected rows, ikke row count. MySQLi har funksjonen mysqli_affected_rows(), som bør gi deg resultatet du trenger.

Nå går jeg ut i fra at koden du snakker om er den samme som du postet sist, det er derfor jeg regner med at $sql har blitt brukt til en INSERT. Det samme gjelder strengt tatt alle operasjoner foruten SELECT; det er affected rows som er interessant der også. mysqli_num_rows() bruker du kun på SELECT-statements.
Endelig tilbake fra jobb reise og kan jobbe med dette igjen.

Takk igjen for gode svar Moff. Men ser at ting fortsatt kan være litt uklart.
Leste litt opp på mysqli_affected_rows. Denne skal trigge hvis en rad har fått en endring, la oss si epost er oppdatert eller raden er blitt slettet.

Hvis jeg f.eks har denne koden:

Kode

if(mysqli_affected_rows($sql) > 0) {
		echo 1;
	} else {
		echo 2;
	}

Så her tenker jo jeg at echo 1 skal bli sendt i respons til Ajax, men her er det echo 2 som blir sendt.
Skal det ikke da være echo 1 siden det er en rad som det ble gjort endringer på?

Dette er fra scriptet med å slette en bruker fra databasen.
Men skal det være selve spørringen man setter mysqli_affected_rows på ($sql) eller er det $stmt ?

Kode

if($stmt = $conn->prepare($sql)) {

	$stmt->bind_param("s", $email);
	$stmt->execute();

	if(mysqli_affected_rows($sql) > 0) {
		echo 1;
	} else {
		echo 2;
	}
}
Limited edition
Moff's Avatar
Jeg ser ikke hvor $sql kommer fra i denne kode-snippen, så jeg må bare gjette hva det er for noe. Jeg antar at $sql er en string som inneholder en spørring, for eksempel noe sånt:

Kode

$sql = "DELETE FROM minTabell WHERE noe = 'noe annet'";
Denne koden ovenfor definerer kun en string, en "dum" tekst som ikke gjør noe som helst av seg selv. Den har ingen egenskaper. Du kan ikke få noe nyttig informasjon fra et string-objekt.

Derfor blir svaret på spørsmålet at du skal bruke $stmt-objektet ditt. $stmt kommer fra $conn-prepare(), som returnerer et MySQLi Statement-objekt. Klikk her for å se dokumentasjonen til dette objektet, hvor du finner alle egenskaper det har (blant annet affected rows).
Sist endret av Moff; 5. mai 2019 kl. 06:44.
Takk for at du alltid tar deg tid @Moff .

Leste en del om affected_rows og sjekke litt rundt i script jobben min tidligere har jobbet med. Ser at jeg måtte kjøre mysqli_stmt_affected_rows() > 0 så kunne jeg kjøre en generell melding om enten success eller feil(brukernavn / epost eksisterer). Vil ikke for detaljerte feilmeldinger.

Nå skal jeg bare rense opp og gjøre koden litt mer sikkert, så skal jeg begynne på en chat implementering inne i applikasjonen, så det blir spennende. Enda mer trøbbel sikkert
God kveld folkens.

Jeg orker ikke lage en ny tråd for å slippe å lage unødvendig mange tråder i forumet.
Har nå tatt tipset og prøvd meg litt på PDO.

Jeg hiver meg bare ut i det og prøver meg fram egentlig. Beste måten å lære på syns jeg.
Nå har jeg da laget 3 scripts. Har ikke gått igjennom alle enda, da den første må gå igjennom først før jeg kan gjøre mer.

Scriptene jeg lager nå er som følger:

send_password_link.php
very_token.php
update_new_password.php


Den første, send_password_link.php ser sånn her ut:


Kode

<?php
error_reporting(E_ALL); ini_set('display_errors', 1);


$servername 	= "localhost";
$username		= "*****";
$password 		= "****";
$dbname 		= "*****";



$pdo = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);

$email = isset($_POST['email']) ? trim($_POST['email']) : '';


$sql = "SELECT 'id', 'email' FROM 'login' WHERE 'email' = :email";

$statement = $pdo->prepare($sql);
$statement->bindValue(':email', $email);

$statement->execute();

$userInfo = $statement->fetch(PDO::FETCH_ASSOC);



if(empty($userInfo)) {
	echo "That email address was not found in our system!";
	exit;
}


$userEmail = $userInfo['email'];
$userId = $userInfo['id'];
$token = openssl_random_pseudo_bytes(16);
$token = bin2hex($token);

$insertSql = "INSERT INTO password_reset_request (user_id, date_requested, token) VALUES (:user_id, :date_requested, :token)";

$statement = $pdo->prepare($insertSql);


$statement->execute(array(
	"user_id" 			=> $userId,
	"date_requested" 	=> date('Y-m-d H:i:s'),
	"token"				=> $token
));

$passwordRequestId = $pdo->lastInsertId();


$verifyScript = "http://URL/verify_token.php";
$linkToSend = $verifyScript . '?uid=' . $userId . '&id=' . $passwordRequestId . '&t=' . $token;

$to = $userInfo['email'];
$subject = "Reset password link (NAME)";

$message = "Here is your link to reset your password <br> If you did not request a new password, disregard this email and consider changing your password <br><br> Link: " . $linkToSend . " ";
$headers = "From: *email*";

$retval = mail($to, $subject, $message, $headers);


?>

Denne koden går ikke igjennom min første IF statement her. Den klarer ikke å hente / sjekke eposten.
Virker som det er flinke personer her med PDO kunnskap, så da tenkte jeg at jeg nå kan hente litt kunnskap om PDO her.
Sist endret av Nikon01; 9. mai 2019 kl. 19:59.
Limited edition
Moff's Avatar
Årsaken til at dette feiler er en syntaksfeil i SQL-spørringen din. Dette ville du fått beskjed om hvis du hadde brukt tilkoblingskoden jeg postet ovenfor. Du setter ikke error mode for PDO, og dermed blir feilmeldingen suppresset, slik at du ikke ser hvor det går galt. Det er en litt pussig feil også, for du har skrevet det korrekt alle andre steder i denne tråden:

Kode

// Feil:
SELECT 'id', 'email' FROM 'login' WHERE 'email' = :email

// Korrekt:
SELECT id, email FROM login WHERE email = :email
Databasenavn og tabellnavn skal ikke skrives med 'anførselstegn', det er kun for variabler. Når du jobber med PDO så bør du sette error mode til exception, slik at det genereres en exception ved denne typen feil:

Kode

$db = new PDO("mysql:host=$host; charset=utf8; dbname=$database", $username, $password, array(
	PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Sett error-mode til exception
	PDO::ATTR_EMULATE_PREPARES => false, // Ikke bruk emulate prepares
	PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC // (Valgfritt) Sett default fetch mode
));
Du kan angi ganske mange ulike innstillinger for PDO på denne måten. Her har jeg tatt med 3 stk. Error mode til exception og emulate prepares til false bør alltid være med. Error mode er som sagt innstillingen som velger hva som skal skje hvis det oppstår feil; det ryddigste er exceptions (kilde: jeg sier at det er sånn). Det betyr at du kan bruke try/catch-blokker for å fange opp feil og handle ut i fra det. Emulate prepares er en feature som handler om hvordan PDO oppfører seg med bruk av prepared statements. Det finnes noen gamle database-drivere der ute som ikke støtter prepared statements, og PDO vil da forsøke å emulere prepared statements. Ikke la deg lure av navnet på innstillingen; hvis denne er satt til true, så vil PDO alltid emulere prepared statements, selv om systemet ditt støtter prepared statements. Hvis du setter denne til false (som er min anbefaling), så vil PDO kun emulere på systemer som ikke støtter prepared statements. Du kan sjekke hvorvidt denne allerede er satt til false, og hvis den er, unnlate å si noe om det - men jeg synes det er best practice å alltid være eksplisitt og sette den til false. Better safe than sorry.
Den tredje innstillingen jeg har tatt med er default fetch mode. Du har skrevet denne linjen:

Kode

$userInfo = $statement->fetch(PDO::FETCH_ASSOC);
Hvis default fetch mode er satt til PDO::FETCH_ASSOC, så ville du kunne skrevet dette i stedet:

Kode

$userInfo = $statement->fetch();
Om du ønsker å bruke denne funksjonen er litt smak og behag. Jeg bruker den som regel ikke, fordi jeg mikser og matcher litt mellom ulike modes når jeg jobber. Hvis du jobber med et prosjekt hvor du kun bruker FETCH_ASSOC, så er det likevel noe du kan vurdere. (For meg så handler det om lesbarhet i kode, det er lettere for meg å se hva som skjer hvis fetch mode alltid er angitt, så jeg slipper å gå til en annen fil for å se hva som er default for prosjektet).

En annen sikkerhets-ting jeg kan tipse om er bruken av openssl_random_pseudo_bytes(). Jeg ville brukt random_bytes() i stedet. Årsaken er først og fremst at random_bytes() er en native PHP-funksjon som ikke krever at du har installert og konfigurert OpenSSL-extensionen. Den andre fordelen er at random_bytes() er kryptografisk sikker (cryptographically secure), i motsetning til openssl_random_pseudo_bytes(). Når du jobber med ting som har med sikkerhet å gjøre, for eksempel nonce-verdier (som dette tokenet er), så bør du bruke en sikker random-funksjon som ikke er forutsigbar. Som du sikkert er klar over så er ikke randomness noe en datamaskin er i stand til å gjøre. Alle verdier som genereres "tilfeldig" er det vi kaller psuedo-random ("liksom-tilfeldig"). Det er likevel noen algoritmer som er så gode at de ikke regnes for å være forutsigbare, og det er disse vi betegner som cryptographically secure. Du har sikkert spilt dataspill som genererer "tilfeldige" kart, og slike spill har gjerne en funksjon for at du kan legge inn en seed til kartet. Hvis to ulike spillere starter spillet med samme seed, så vil de få nøyaktig samme kart, selv om kartet er "tilfeldig" generert. Dette demonstrerer hvordan noe kan være tilfeldig, men likevel 100% forutsigbart.

I likhet med hash-algoritmer, som vi har diskutert tidligere, så er det viktig å finne en balanse mellom hva som er trygt, og hva som tar kortest mulig tid for datamaskinen å regne ut. Derfor har vi algoritmer som er trygge (og trege) og algoritmer som er utrygge og lynraske. For å generere kartfiler i et spill trenger du ikke noe som er trygt; det spiller ingen rolle. For nonce-verdier, passord og slike ting, da er sikkerhet veldig viktig.

$insertSql-en din er forresten et eksempel på en situasjon hvor du ikke trenger å bruke prepared statements, hvis du ikke vil. Du bør uansett være konsekvent i koden din; her bruker du execute() med et array, i stedet for å bruke bind-funksjonen slik du gjør lengre opp i koden. Det spiller ikke så voldsomt stor rolle hva du gjør, men du bør i alle fall gjøre det samme hver gang.

Personlig anbefaler jeg at du bruker bindParam(). Du har brukt bindValue(), som er den samme funksjonen, men den binder verdien av variabelen din i stedet for referansen til den. Så, hva er forskjellen på bind-by-value og bind-by-reference? Se her:

Kode

$var = 'laks';
$q->bindParam(':var', $var);
$var = 'bever';
// Bundet verdi er nå 'bever'

$var = 'laks';
$q->bindValue(':var', $var);
$var = 'bever';
// Bundet verdi er 'laks', selv om verdien til $var nå er 'bever'
Igjen, dette er smak og behag, men du bør i alle fall være konsekvent og bevisst på hvilken av dem du bruker. Jeg foretrekker å bruke bindParam(), fordi jeg da har flere muligheter til å strukturere koden min mer effektivt i enkelte situasjoner. Det skal imidlertid sies at det i noen tilfeller kan være greit å bruke bindValue() hvis du prøver å skrive kortest mulig kode, fordi du MÅ ha en referanse for å bruke bindParam(). Du kan med andre ord ikke bruke noen verdier eller uttrykk i bindParam():

Kode

$q->bindParam(':var', 0); // Error: Cannot pass by reference yada yada yada ...
Du lagrer en dato sammen med tokenet ditt; dette kan du droppe til fordel for en TIMESTAMP-kolonne i databasen din. Opprett en kolonne, sett den som en TIMESTAMP-type og sett default til CURRENT_TIMESTAMP. Hver gang du nå gjør en INSERT mot denne tabellen, så vil du få en timestamp rett inn i denne kolonnen. Jeg pleier å kjøre SELECT-statements med UNIX_TIMESTAMP() på denne kolonnen hvis jeg trenger å sjekke tidspunkt:

Kode

SELECT userId FROM tokens WHERE UNIX_TIMESTAMP(expires) > UNIX_TIMESTAMP() LIMIT 1
Hvis vi tenker at kolonnen "expires" inneholder en timestamp for når tokenet utløper (ikke lengre er gyldig), så vil du her bare select-e tokens som ikke har gått ut på dato. Dette fordrer selvsagt at du har satt inn en timestamp i denne kolonnen som er frem i tid, ikke CURRENT_TIMESTAMP:

Kode

$timestamp = time() + 60 * 15; // 15 minutter fra nå
$timestamp = date('Y-m-d H:i:s', $timestamp); // Konverter fra UNIX_TIMESTAMP til MySQL TIMESTAMP

// INSERT ...
Avsluttningsvis så er det 3 ting til jeg legger merke til, som kan være problematisk.

1 - Eksponering av bruker-ID-er til klienten. Du sender en link som inneholder ID-nummer fra databasen, både fra password_reset_request -og fra login-tabellen. Er dette strengt tatt nødvendig? Nei. Nei, det er det ikke. Du har generert et tilfeldig token, og dette tokenet er bundet til en bruker-ID i databasen din. Hvis en bruker sender deg dette tokenet, så vet du med 99,99% sikkerhet at det er korrekt bruker, og du kan gå ut i fra at bruker-ID-en fra databasen er korrekt. Om klienten også må presentere bruker-ID, så gir ikke dette deg noe ekstra sikkerhet uansett; hvis noen har klart å få tak i tokenet, så har de garantert også tilgang til bruker-ID-en som hører til (fordi alt ligger i samme link). Så, designtips: Generer en sikker nonce-verdi med random_bytes. Gjerne noe som er litt mer enn 16 bytes; jo lengre det er, jo vanskeligere er det å gjette. Du trenger ikke 128 bytes, liksom, men kanskje 32. Kanskje 64. Noter deg IP-adressen til klienten som har bedt om reset, og verifiser at det er samme IP som sender tokenet tilbake til deg. Da kan du være 99,9999% sikker på at det er samme klient. IP får du fra $_SERVER['REMOTE_ADDR']. Sørg for at kolonnen du lagrer IP i er minst 45 tegn for å kunne lagre alle IPv6-formater (spesifikt "IPv4-rutete" IPv6-adresser). Ta også å hiv inn en UNIQUE-key på kolonnen for token, så du er helt sikker på at du ikke kan ha to like tokens (for det ville være katastrofalt). Du kan ev. også sjekke om tokenet du har generert eksisterer fra før, og da lage et nytt et hvis du har klart å generere det samme to ganger. Sannsynligheten for at dette skjer er helt hinsides liten, men det kan jo skje. (Not really.)

2 - Bruk HTTPS. Jeg ser at linken din bruker HTTP. I 2019 så er dette litt krise. Du kan ha så trygge algoritmer du vil, men hvis du sender all informasjon over internett uten kryptering, så hjelper det fint lite. Du kan få gratis TLS-sertifikater fra tjenester som Let's Encrypt, så det er bare å kjøre på.

3 - mail()-funksjonen er litt... vel, den fungerer ikke så bra. 85% av all e-post som sendes på nettet er spam akkurat nå. Hvis du sender e-post uten en verifisert SMTP-server, så vil det alltid bli betraktet som spam. Det beste du kan håpe på er at e-posten kommer fram til spam-mappen, men i mange tilfeller vil den nok bare bli blokkert før den i det hele tatt kommer frem til brukeren. Det å sende automatisert e-post er rett og slett vanskelig, du kan aldri garantere at det ikke havner i spam-filter. Det du kan gjøre er å bruke en skikkelig e-posttjeneste hvor du har en SMTP-server med DKIM, SPF og PTR. Det er litt dill-dall å konfigurere, og du bør også da ta i bruk et e-postbibliotek i PHP for å gjøre tilkobling og utsending lettere.
Trådstarter
Igjen @Moff. Tusen takk for godt svar.
Du svarer ikke bare langt, men også veldig detaljert og godt.

Skal studere alt her også ser jeg om jeg får dette til til slutt.

Har lest godt igjennom alt det du skriver her nå Moff og det er veldig gode og lærerike tilbakemeldinger å få.
PDO virker lettere og bedre enn hva rein PHP gjør som jeg kan se nå (Har ikke holdt på så lenge med dette).

Jeg har gjort disse endringene du snakket om og lest litt opp om hver funksjon du nevner her og ser klart forskjellen på det.
Skal lese litt mer opp på det og få litt mer klarhet i alle forskjeller og best bruksmåte.


Etter å ha sjekket og endret koden, får jeg fortsatt samme feilmelding.
Tok bort $email variabel en og la bare inn $email = 'test@test.com' og da fungerte dette helt fint.
Så det kan virke som denne her ikke fungerer som den skal, men jeg klarer virkelig ikke å se hvorfor.


Kode

$email = isset($_POST['email']) ? trim($_POST['email']) : '';
Hele denne seksjonen før den som feiler:

Kode

$pdo = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password, array(
		PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
		PDO::ATTR_EMULATE_PREPARES => false,
		PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
));

$email = isset($_POST['email']) ? trim($_POST['email']) : '';


$sql = "SELECT id, email FROM login WHERE email = :email";

$statement = $pdo->prepare($sql);
$statement->bindParam(':email', $email);

$statement->execute();

$userInfo = $statement->fetch();



if(empty($userInfo)) {
	echo "That email address was not found in our system!";
	exit;
}
Sist endret av Nikon01; 10. mai 2019 kl. 16:44. Grunn: Automatisk sammenslåing med etterfølgende innlegg.
Limited edition
Moff's Avatar
Når du sier at du får en feilmelding, tenker du da på "That email address was not found in our system!"? Hvis du kun ser den, og ikke en PHP-error, så tyder det på at koden din fungerer som den skal. Kanskje ikke slik det var tenkt at den skulle fungere, men det er i alle fall ikke noen syntaksfeil i den. Jeg har testet koden fra siste post, og den fungerer helt fint, også når jeg har en gyldig e-postadresse som jeg forventer å finne i databasen.

Hvis du opplever problemer med at den rett og slett ikke finner e-postadresser som du forventer at den skal, sjekk innholdet i $_POST['email'] nøye. En nyttig debug-kode for å se hva du får inn av POST-data er dette:

Kode

print_r($_POST);
Dette vil dumpe alle variabler i hele $_POST-arrayet. Jeg har en mistanke om at $_POST['email'] ikke blir satt riktig, og da mest sannsynlig fordi name-attributten i form-en din ikke har riktig navn.
Trådstarter
Hei Moff.

Kjører ting ganske enkelt for testing her nå. Nå tester jeg med en fastsatt Epost bare for å sjekke resten av scriptet før jeg begynner å sjekke ut $_post['email'] saken.

Kode

$pdo = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password, array(
		PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
		PDO::ATTR_EMULATE_PREPARES => false,
		PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
));

$token = isset($_GET['to']) ? trim($_GET['to']) : '';

$_SESSION['token'] = $token;

var_dump($token);

$sql = "SELECT reset_token FROM login WHERE reset_token = :token";



$statement = $pdo->prepare($sql);


$statement->execute("token"	=> $token);

$requestInfo = $statement->fetch();


if(empty($requestInfo)) {
	echo 'invalid request';
	exit;
}



header('Location: index.php?to=' . $token);
exit;
Den over her er jo da ganske enkel.
Denne mottar token i URL'en som den sjekker opp mot databasen om den eksisterer.

Her får jeg $requestInfo som er tom. Invalid request er det denne kjører.