Für meine AAUShare Applikation brauch ich ja wie bereits in meinen letzten Eintrag erwähnt eine User Authentfizierung. Dies habe ich mit 2 Varianten gelöst. Zuerst gibt es den einfachen Login über eine eigene User Tabelle mithilfe von Doctrine. Um die zu ermöglichen werden mehrere Schritte benötigt. Zuerst muss das application.config.php File erweitert werden
'doctrine' => array(
....,
'authentication' => array(
'orm_default' => array(
'object_manager' => 'Doctrine\ORM\EntityManager',
'identity_class' => 'AAUShare\Entity\User',
'identity_property' => 'email',
'credential_property' => 'password',
),
),
),
Hierbei wird folgendes gemacht: Es wird der Standard EntityManger hergenommen, mit der Klasse User, es wird die Email Adresse als Identity „Username“ hergenommen und das Passwordfeld ist hier logischerweiße das password.
Die Auth – Methode im Controller sieht wie folgt aus:
$form = $this->getForm();
$redirect = 'login';
$request = $this->getRequest();
if ($request->isPost()){
$form->setData($request->getPost());
if ($form->isValid()){
//check authentication...
$config = $this->getServiceLocator()->get('Config');
$passwordhashed = crypt($request->getPost('password'), /** app salt here **/);
//hier wird der Auth Adapter von Doctrine geholt, so wie wir ihn vorher konfiguriert haben
$adapter = $this->getServiceLocator()->get('doctrine.authenticationadapter.orm_default');
$adapter->setIdentityValue($request->getPost('email'));
$adapter->setCredentialValue($passwordhashed);
$this->getAuthService()->setAdapter($adapter);
$result = $this->getAuthService()->authenticate();
foreach($result->getMessages() as $message)
{
//save message temporary into flashmessenger, handle errors here
}
if ($result->isValid()) {
//Dot some stuff for valid users here
}
$this->getAuthService()->setStorage($this->getSessionStorage());
$this->getAuthService()->getStorage()->write($request->getPost('email'));
}
}
}
Es wird im der Request abgefangen und behandelt, wichtig ist hier auch das der Doctrine Adapter den ich vorher konfiguriert habe, hier über den ServiceManager / SerivceLocator geholt wird. Dort ist alle schon vorkonfiguriert und es wird nur mehr das zu testende Passwort und Username gecheckt. Weiters wird im AuthService Storage handling betrieben, d.h. es wird die Email Adresse hinterlegt. Der User Selbst wird auch in die Session gesteckt, die passiert aber bereits im Adapter
Login mit Facebook
Neben dem normalen Login mit Email und Passwort, habe ich noch einen Login über OAuth von Facebook implementiert. Hier wird das Facebook Konto als Login für die Applikation hergenommen. Ich habe mehrere fertige Lösungen für Zend2 ausprobiert, jedoch hat keine für mich funktioniert. Man braucht jedoch für diesen Login bei Facebook nur in der Doku nachzusehen, um zu erkennen das dafür mit simplen nativen PHP Funktionen alles erledigt werden kann. In Zend muss dann nur mehr ein eigener Adapter, der ja das Authentifizieren im AuthenicationService durchführt, implementiert werden, dieser sieht wie folgt aus. Der erste Schritt zum Facebook Login sieht dann so aus im Controller aus:
public function fbAuthAction(){
$config = $this->getServiceLocator()->get('Config');
$session = new Container('fb');
$fbsessval = md5(uniqid(rand(), TRUE));
$session->offsetSet('state',$fbsessval); // CSRF protection
$fbapiconfig = $this->getServiceLocator()->get('AAUShare\Bean\FBApiConfig');
$dialog_url = $fbapiconfig->getUrl($fbsessval);
return new ViewModel(array('dialog_url'=>$dialog_url));
}
Es wird hier über das Config file, die Keys für die Facebook Api ausgelesen und der Link passend zusammen gebaut
(Sollte so aussehen: https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_uri=YOUR_REDIRECT_URI&state=SOME_ARBITRARY_BUT_UNIQUE_STRING), der State ist ein zufällig generierter String der beim Callback wieder benötigt wird und daher in die Session abgelegt wird.
Weiter geht es mit der callback – Methode im Controller. Hier gelangt man nach dem Login auf wieder zurück.
$session = new Container('fb');
$sstate = $session->state;
if($sstate && ($sstate === $this->getStateFromRequest())) {
$this->getServiceLocator()->get('AAUShare\Service\FacebookService')->authWithReqquestCode($this->getRequestCode());
} else { //TODO: ErrorHandling
echo("The state does not match. You may be a victim of CSRF.");
$code = $_REQUEST["code"];
//echo $code;
}
Es wird hier die State Variable wieder aus der Session geholt sowie mit getRequestCode aus der $_REQUEST Variable der Wert von Code geholt der von Facebook generiert wird. Im FacebookService passiert dann der eigentliche authentifizierungs Vorgang in der Applikation statt.
....
$config = $this->getSerivceManager()->get('AAUShare\Bean\FBApiConfig');
$response = file_get_contents($config->getGraphUrl($requestCode));
$params = null;
parse_str($response, $params);
$adapter = $this->getSerivceManager()->get('AAUShare\Adapter\FBAuthAdapter');
$adapter->setAccesstoken($params['access_token']);
$authenticationService = new \Zend\Authentication\AuthenticationService();
$authenticationService->setAdapter($adapter);
return $authenticationService->authenticate();
Es wird wieder die Config geladen, danach werden über die Graph API von Facebook ein Accestoken geholt. Anschließend werden die geholten Daten weiter verarbeitet. Es wird durch den soeben erlangten Accesstoken, der den Zugriff auf die Userdaten auf Facebook erlaubt, genutzt um die Daten vom User zu holen. Anschließend wird der eigene Adapter geholt und dem Service übergeben. Hier noch kurz die Methode vom Adapter. Im handleFbAuth wird also nur mehr überprüft ob der User bereits in der Datenbank vorhanden ist, wenn nicht wird er angelegt. Wichtig hier auch noch der Return Wert „Result(\Zend\Authentication\Result::SUCCESS, $user)“ hier das User Objekt abgelegt, über das man später in der Applikation mit dem AuthenticationService->getIdentity() zugreifen kann (wird in der Session abgelegt)
public function authenticate()
{
$graph_url = "https://graph.facebook.com/me?access_token=".$this->accesstoken;
$user_json = json_decode(file_get_contents($graph_url));
$user = $this->getUserService()->handleFbAuth($user_json);
if($user != null){
return new \Zend\Authentication\Result(\Zend\Authentication\Result::SUCCESS, $user);
}
return new \Zend\Authentication\Result(\Zend\Authentication\Result::FAILURE_IDENTITY_NOT_FOUND, array("failure on facebook login"));
}
Ab diesen Punkt kann man mit beiden Lösungen prüfen ob der User eingeloggt ist und welcher User eingeloggt ist (AuthenticationService->hasIdentity() und AuthenticationService->getIdentity()