Sessies en autorisatie

Inleiding Sessies Autorisatie

Inleiding

Dit hoofdstuk gaat over sessies en autorisatie. Autorisatie is erg belangrijk in webapplicaties. Het is bijvoorbeeld erg prettig als alleen jij bij je Facebook account gegevens kunt wijzigen of, nog meer prive, alleen jij in een bankapplicatie geld kan overmaken vanuit jouw bankrekening. Je moet je in deze gevallen dus aanmelden met een username en wachtwoord. Dit alles liefst in een versleutelde omgeving (https) en met een alleen bij jou bekend password. In dit hoofdstuk voldoen we aan deze voorwaarden niet. Websrv201 is niet als htrps server ingericht en om het leven voor jou als programmeur iets eenvoudiger te laten zijn versleutelen we de wachtwoorden in de voorbeelden nog niet.

Sessies en autorisatie hebben dus iets te maken met de afhandeling van een reeks van acties van gebruikers en toegang van gebruikers tot websites.

Sessies in php zijn al aan bod gekomen in de cursus Opdracht PHP H4/V5 hoofdstuk 8. Heb je dat hoofdstuk niet gedaan dan is het noodzakelijk dat je dat deel eerst doorneemt.

Aan het einde van dit hoofdstuk weet je :

  • wat een sessie is;
  • wat autorisatie is;
  • hoe je vanuit een bladzijde een hele website controleert;

Introductie sessie

We herhalen hier kort de betekenis van sessies.

In een dagelijkse situatie heb je misschien wel meegemaakt dat je bij een aanvraag bij een loket een nummer kreeg toegewezen. Met dat nummer kon je verder de procedure in totdat je aanvraag was afgerond. Daarna gooide je je nummer weg.

Binnen websites kunnen we dit principe ook gebruiken. Je logt in om bijvoorbeeld een bestelling uit te voeren, dan is het handig als de volgende pagina (ook nog weet) dat jij als gebruiker de bestelling plaatst. PhP kan zo'n nummertje uitdelen in de vorm van sessies. Een sessie slaat dus tijdelijk bepaalde gegevens op bij de webserver (waar de website wordt gehost) en zal (als het goed is) aan het einde van de rit worden weggegooid.

Het is belangrijk dat sessies goed staan ingesteld om te voorkomen dat ze na enkele uren nog steeds bestaan of dat een andere gebruiker jouw bestelling of andere gegevens kan lezen.

In het voorbeeld hiernaast (voorbeeld 5 uit Opdracht PHP H4/V5 hoofdstuk 8) wordt in één document een sessie aangemaakt. Kun je één gebruikers actie doen en kun je de sessie afsluiten. In het voorbeeld wordt een array gebruikt om tijdelijk gegevens op te slaan. Deze aanpak moet je ook gaan gebruiken voor het vullen van een winkelwagen. De code van het voorbeeld staat hieronder.

Opdrachten
  1. Sla de code in sessie.php.
  2. Leg uit waar in de code er een tabel wordt getoond bij invullen van gegevens en het indrukken van de knop.
  3. Als je twee keer de zelfde naam invoert maar wel andere eigenschappen komt er niet een rij bij. Hoe komt dat?
  4. Na het indrukken van stop zijn de gegevens verdwenen. Waar gebeurt dat in de code?
  5. Verander het voorbeeld zo dat, in plaats van een naam en eigenschappen, een productnummer en een aantal kan worden ingevoerd. Laat in een tabel productnummer en aantal zien.
sessie
<?php
//opslaan als sessie.php
function voegToe(&$naam,&$positieve,&$verbeterpunt)
{
 $result="";
 if(isset($_POST['naam']) && !empty($_POST['naam']) )
 {
   $naam=trim($_POST['naam']);
   $verbeterpunt="";
   $positieve="";
   if(isset($_POST['verbeterpunt']) && !empty($_POST['verbeterpunt']) )
   {
    $verbeterpunt=trim($_POST['verbeterpunt']);
   }
   if(isset($_POST['positieve']) && !empty($_POST['positieve']) )
   {
    $positieve=trim($_POST['positieve']);
   }
   $_SESSION['eigenschappen'][$naam]=array($positieve,$verbeterpunt);
 }
 $result="<table>";
 $result.="<tr><td>Naam</td><td>Positieve eigenschap</td><td>Verbeterpunt</td></tr>";
 foreach(  $_SESSION['eigenschappen'] as $key => $value )
 {
  $result.="<tr><td>$key</td><td>{$value[0]}</td><td>{$value[1]}</td></tr>";
 }
 $result.="</table>";
 return $result;
}

$naam="";
$positieve="";
$verbeterpunt="";
$uitvoer="";
$actie="";
$status="";
session_start(); // sessie starten
//We testen of een door ons gemaakte sessie nog niet bestaat
if(!isset($_SESSION['mijnid']) )
{
  $sessionid= $_SESSION['mijnid'] = session_id(); // uniek nummer genereren
  $_SESSION['eigenschappen']=array();
  $uitvoer.= "Sessie gegenereerd.<br/>";
  $status="gestart";
}

if(isset($_POST['actie']))
{
 $actie =$_POST['actie'];
}

switch($actie)
{
 case "Stop": // session_unset(); 
              $uitvoer.="Stop: Ik ga stoppen gestopt met sessie:".
                        $_SESSION['mijnid'].":<br/>";session_destroy();
              $uitvoer.="! Daarmee worden alle personen gewist!";                    
              break;
 case "Voeg toe":
              $uitvoer.= voegToe($naam,$positieve,$verbeterpunt);
              break;      
}
echo <<<END
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>    
<title>Page title</title>
</head>
<body>
<h1>Mijn sessie applicatie: Overzicht eigenschappen van mijn vrienden:</h1>
<form action="{$_SERVER['PHP_SELF']}" method="post">
<table summary="">
<tr>
<td>Geef een naam:</td>
<td><input name="naam" value="$naam"/></td>
</tr>
<tr>
<td>Geef een positieve eigenschap:</td>
<td><input name="positieve" value="$positieve"/></td>
</tr>
<tr>
<td>Geef een verbeterpunt:</td>
<td><input name="verbeterpunt" value="$verbeterpunt"/></td>
</tr>
<tr>
<td colspan=2 ><input type="submit" name="actie" value="Voeg toe"/>
 <input type="submit" name="actie" value="Stop"/>
</td>
</tr>
</form>
<p>Overzicht eigenschappen</p>
$uitvoer
</body>
</html>
END;
?>

autorisatie

We gaan even verder bij de analogie van het loket. Soms moet je je eerst kunnen legitimeren voor je verder mag in de procedure. Kun je je niet legitimeren dan krijg je geen toegang tot de afhandeling. Dit proces noemt men autorisatie.

Bij veel websites heb voor bepaalde delen een gebruikersnaam en wachtwoord nodig. De server van de website controleert dan of de gebruikersnaam en wachtwoord geldig zijn. Deze gegevens moeten dan wel ergens zijn opgeslagen. In de database 'garagebedrijf' is zijn in de klanten tabel de velden loginname en password aanwezig. In de code index.php is een voorbeeld aanwezig om acties door klanten te laten uitvoeren pas als ze zijn ingelogd.

Opdrachten
  1. Maak een nieuwe map admin aan. Kopieer database.php, product.php en algemeen.php naar deze map. Sla de onderstaande bestanden index.php en autorisatie.php op in deze map. Ga met de browser naar index.php, login met gebruiker "HenkJan" en wachtwoord "a" en druk op de knoppen.
  2. Zoek de overeenkomst tussen index.php met sessie.php
  3. Waar haalt de knop tooninfo het gegeven van de klant vandaan?
  4. Hoe is de uitvoer geregeld?
voorbeeld
<?php
// Opslaan als index.php 
// Start sessie
session_start();
//Laad database code
require_once("database.php");
//Laad algemene code
require_once("algemeen.php");
//Laad code voor de acties met betrekking tot producten die we op de database gaan doen
require_once("product.php");
//Laad code voor de acties met betrekking tot autorisatie
require_once("autorisatie.php");

// Zorg ervoor dat de bladzijde altijd vanuit de server wordt ververst
// Zie https://www.imperva.com/learn/performance/cache-control/ en
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
header("Cache-control: no-store");

// declareer variabelen

$menu = "";
$uitvoer="";
$user="";
$password="";
$toegelaten=false;
$toegelaten_fout_boodschap="";
$uitvoer="";
$actie="";

// lees de actie in
if( isset($_POST['actie']) && !empty($_POST['actie'])  )
{
  $actie=$_POST['actie'];
}

// test of user als is ingelogd
// Voeg hier eigen commentaar toe. 
if(isset($_SESSION["access"]) && $_SESSION["access"] == "toegelaten" &&
         $_SESSION["rol"] && $_SESSION["rol"] == "medewerker")
{
  $toegelaten=true;
  $user=$_SESSION["user"];
  $menu = getMenu(); // Haal het menu door de functie getMenu() uit acties.php op te roepen
}
else // zo niet dan komt de bezoeker mogelijkerwijs van het inlog scherm.
{
  // Voeg hier eigen commentaar toe. 
  if($actie=="Log in" && checkLogin() )
  {
   $menu = getMenu(); 
  }
  $uitvoer= $toegelaten_fout_boodschap."<br/>";
}

// Als je bent toegelaten toon je de hoofdbladzijde anders
// toon je het inlogscherm
if($toegelaten)
{
  // Als ingelogd persoon kun je de volgende acties uitvoeren
  switch($actie)
  {
    case "Afmelden": afmelden(); 
  		$uitvoer = getLoginScreen();
  		$menu=""; 
  		break;
    case "Toon info": $uitvoer=tooninfo(); break;
    case "verwijder":
  		$uitvoer=toonArtikelVerwijderTabel(true); // Voer de functie toonArtikelVerwijderTabel() in acties.php uit
  		break;
    case "Bevestig Verwijderen":
  		$uitvoer=productVerwijderen(); // Voer de functie ProductWijzigen() in acties.php uit
  		break;
    case "wijzig":
  		$uitvoer=toonArtikelWijzigTabel(array_fill(0,4,""),true); // Voer de functie toonArtikelWijzigTabel met 4 nu nog lege fouten in acties.php uit
  		break;
    case "Wijziging Doorvoeren":
  		$uitvoer=productWijzigen(); // Voer de functie ProductWijzigen() in acties.php uit
  		break;
    case "Invoeren":
  		$uitvoer=artikelInvoeren(); // Voer de functie ProductInvoeren() in acties.php uit
  		break;
    case "Voeg Product Toe":
  		// Voer de functie toonArtikelInvoerTabel() in acties.php uit met 4 lege waarde fout paren
  		$uitvoer=toonArtikelInvoerTabel(legeInvoerEnFout(4),true);  
  		break;
    case "Toon Artikelen": $uitvoer=toonArtikelenLijst(); break;
    case "Toon VerbindingStatus": $uitvoer = toonVerbindingStatus(); break;
    default: $uitvoer = "<h1>Welkom bij ons garagebedrijf</h1>"; break;
  }
}
else
{
  // Als niet ingelogd persoon kun je de volgende acties uitvoeren
  switch($actie)
  {
    case "Registreren":  $uitvoer = "De functionaliteit Registreren moet nog worden gemaakt"; 
    default:  $uitvoer .= getLoginScreen(); break;
  }
}
echo <<<END
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />    
<title>Garagehuis</title>
</head>
<body>
<form action="{$_SERVER['PHP_SELF']}" method="POST">
$menu
<hr>
$uitvoer
</form>
</body>
</html>
END;
?>
Vervang in algemeen.php de functie getMenu() door de volgende versie
function getMenu()
{
 $uitvoer=<<<END
 <table class='menu' summary="">
  <tr>
    <td><input type="submit" name="actie" value="Toon Artikelen">
    </td>
    <td><input type="submit" name="actie" value="Voeg Product Toe">
    </td>
    <td><input type="submit" name="actie" value="Afmelden">
    </td>
    <td><input type="submit" name="actie" value="Toon info">
    </td>
  </tr>
 </table>
END;
 return $uitvoer; 
}

autorisatie.php
<?php
// Opslaan als autorisatie.php
/**
 * autorisatie.php bevat de code nodig voor het aanmelden bij de applicatie
 * nodig is code uit database.php
 */
 
global $toegelaten_fout_boodschap;
global $user;
global $password;
global $toegelaten;
 
/**
 * checkLogin is de eerste functie die wordt aangeroepen in het autorisatie proces.
 * Zijn de formulier gegevens aanwezig dan wordt via de checkUser functie de aanwezigheid van 
 * de gebruiker in de database worden gecontroleerd.
 */
function checkLogin()
{
  global $toegelaten_fout_boodschap;
  global $user;
  global $password;
  global $toegelaten;
  $toegelaten=false;
  /* Haal gebruikersnaam en wachtwoord uit het formulier  */
  // Voeg hier eigen commentaar toe. 
  if( isset($_POST['loginname']) && !empty($_POST['loginname'])  )
  {
    $user = $_POST['loginname'];
    if( isset($_POST['password']) && !empty($_POST['password']) )
    {
     $password = $_POST['password'];
     // Voeg hier eigen commentaar toe. 
     if($toegelaten=checkUser($user,$password))
     {
       /* toegang is verkregen */
	   $toegelaten_fout_boodschap = "$user, welkom bij mijn webapplicatie";
     }
     else
     {
	   /* toegang geweigerd */
	   afmelden();
     }
    }
  }
  return $toegelaten; 
}


/**
 * checkUser: test of de gebruiker en zijn wachtwoord bestaan.
 * Zo niet laat het inlog scherm weer zien anders krijg je toegang
 * NB: in garagebedrijfnieuwenhuis voorbeeld is bij de klanten het 
 * wachtwoord ingesteld op "123<Achternaam>"
 */
function checkUser($user,$password)
{
  global $toegelaten_fout_boodschap;
  $toegelaten=false;
  $melding=""; // initialisatie van variabele om connectie foutmelding in te plaatsen
  global $db; // Haal een verwijzing naar de huidige connectie in database.php
  //verbinding maken met database
  if(!$db) maakVerbinding($db,$melding);
  if ($db)
  {
     // maak $user en $password veilig voor SQL
	 $user=$db->real_escape_string($user);
     $password=$db->real_escape_string($password);
     /* De SQL vraag voor de toegang */
     $query = "SELECT id,loginname,password FROM medewerker ".
              " WHERE loginname = '$user'  AND password = '$password'";
  
     /* Stuur de vraag naar de database manager */
     $result = $db->query($query);
  
     /* Als er een resultaat komt dan is toegang toegestaan anders niet */
     if (list($id,$loginname,$password)=$result->fetch_row())
     {
        $toegelaten_fout_boodschap= "Toegang verkregen: Welkom, $user!";
        $toegelaten=true;
        // Voeg hier eigen commentaar toe. 	
        $_SESSION["access"] = "toegelaten";
        $_SESSION["user"] = $user;
        $_SESSION["medewerkernummer"] = $id;
		$_SESSION["rol"] = "medewerker";		
     }
     else
         $toegelaten_fout_boodschap= "Toegang gewijgerd: Probeer het nogmaals.";
    
     verbreekVerbinding();
  }
  else
  {
     $toegelaten_fout_boodschap= 
     "interne fout: Probeer in contact te komen met het beheer van deze site!";
  }
  return   $toegelaten;
}

/**
 * afmelden: functie die moet worden aangeroepen als je wil afmelden.
 */
function afmelden()
{
  // Maak de Sessions variabele op de server leegleeg
  $_SESSION = array();
  // ruim alles op bij de client
  session_destroy();
}

/**
 * tooninfo: functie toont de gegevens van de gebruiker.
 */
function tooninfo()
{
  $uitvoer="";
  $melding=""; // initialisatie van variabele om connectie foutmelding in te plaatsen
  global $toegelaten_fout_boodschap;
  global $db; // Haal een verwijzing naar de huidige connectie in database.php
  //verbinding maken met database
  if(!$db) maakVerbinding($db,$melding);
  if ($db)
  {
     /* SQL statement to query the database */
     // Voeg hier eigen commentaar toe. 	
     $query = "SELECT * FROM medewerker ".
              " WHERE id = '".$_SESSION["medewerkernummer"]."'";
  
     /* query the database */
     $result = $db->query($query);
     if($result)
     {
       /* Allow access if a matching record was found, else deny access. */
       if (list($id,$loginname,$password,$voornaam,$achternaam,$straat,$huisnummer,$postcode,$woonplaats)=$result->fetch_row())
       {
        $uitvoer .= "Jij bent $voornaam $achternaam en woont op $straat in $woonplaats.<br/>".
                    "Als gebruikersnaam heb je: $loginname Je password is geheim.";
       }
     }
     else
         $uitvoer = "Fout in query.";
    
     verbreekVerbinding();
  }
  else
  {
     $uitvoer.= 
     "interne fout: Probeer in contact te komen met het beheer van deze site!".
      mysql_error($db);
  }
  return   $uitvoer;
}

/**
 * getLoginScreen: geeft de html code voor het inlogscherm.
 */
function getLoginScreen()
{
 global $toegelaten_fout_boodschap;
 global $user;
 
 $uitvoer=<<<END
<h1>Login in mijn applicatie</h1>
<h2>$toegelaten_fout_boodschap</h2>
<form action="{$_SERVER['PHP_SELF']}" method="post">
  <label for="loginname">Login naam</label>
  <input type="text" name="loginname" id="loginname" value="$user"/>
  <br />
  <label for="password">Password</label>
  <input type="password" name="password" id="password" />
  <br />
  <input type="submit" name="actie" value="Log in" />
</form>
END;
 
 return $uitvoer;
}

//einde van het PhP blok
?>

Opdracht
webwinke

Er zijn heel veel kant en klare webwinkel applicaties bijvoorbeeld binnen de Bitnami tools. Een zoektocht op duckduckgo met beste gratis webshop software levert ook genoeg keuzes.

Wij willen echter dat voor je een groot pakket pakt eerst leert nadenken over de onderdelen die je in zo'n webwinkel moet implementeren. Daarom gaan jullie in groepsverband een klanten en een administratie website maken met behulp van het geleerde hierboven. Ook een andere eigen webapplicatie met database ondersteuning is natuurlijk mogelijk. Een beginnetje vind je hier.