Skip to main content

PHP och BankID

28 Feb 2017 -
OBS
BankID har bytt API-ramverk sedan jag skrev denna, så den här metoden funkar inte längre.
Jag hade en hel del problem med BankID och PHP innan jag plöjde igenom hela specen och med lite trial and error listade ut grunderna.
Här är kodexempel på implementation av BankID och SOAP via PHP. Själva delen med AJAX och anrop mot "bankid.php" får du lösa själv, men tanken är alltså att via AJAX så anropar du bankid.php som i sin tur returnerar svar på din förfrågan. Så första anropet är med ett personnummer och initiering av förfrågan, och sedan anropar du bankid.php med förfrågan om status var x:e sekund till exempel.

Certifikat

För att få ett BankID-certifikat så använder du en Java-applikation som tillhandahålls av din kontaktperson på din bank, den java-applikationen använder du för att ange ditt företagsnamn, lösenord etc, och det skapar en "p10"-fil som du skickar in till din kontaktperson, som i sin tur skickar tillbaka ett certifikat.
Det certifikatet är (oftast) i .cer-format och ibland har det varit binärt och ibland ascii, men när jag får certifikatet så kör jag detta:
~> openssl x509 -inform der -in bankcertifkat.cer -out mittcertifikat.pem
Det skapar en mittcertifikat.pem och en mittcertifikat.key, problemet här är att SOAP i PHP förväntar sig en certifikatfil, så dessa måste slås ihop till en fil, som då ser ut så här:
-----BEGIN CERTIFICATE----- *KODAT* -----END CERTIFICATE----- -----BEGIN PUBLIC KEY----- *KODAT* -----END PUBLIC KEY----- -----BEGIN RSA PRIVATE KEY----- *KODAT* -----END RSA PRIVATE KEY-----
Med andra ord så måste certifikatet, den publika nyckel och den privata nyckeln ligga i samma fil. Kom ihåg att lägga den här filen på din server på ett ställe där Apache inte kan surfa till, så ingen kan komma åt den. Sen kan du sätta igång!

bankid-klassen

Min kod är avhängigt mitt eget CMS, så en del variabler får anpassas ($in är en samling av $_GET och $_POST t.ex).
class bankid {
    private $client;
    public $debug = false;
    public $status;
    function __construct($url, $certificate, $passphrase){
      try {
        $this->client = new SoapClient($url, array("local_cert" => $certificate, "passphrase" => $passphrase, "trace" => 1));
        $this->status = "ok";
      } catch (Exception $e){
        $this->status = "error";
        if ($this->debug) $this->status .= " ". $e->getMessage();
      }
    }
    public function sign($social, $message){
      $parameters = array(
        "parameters" => array(
          "personalNumber" => $social,
          "userVisibleData" => base64_encode(encode($message))
        )
      );
      return $this->call("Sign", $parameters);
    }
    public function collect($ref){
      $parameters = array(
        "orderRef" => $ref
      );
      return $this->call("Collect", $parameters);
    }
    private function call($function, $parameters){
      try {
        $response = $this->client->__soapCall($function, $parameters);
        if ($this->debug){
          error_log("REQUEST:\n");
          error_log($this->client->__getLastRequest() . "\n");
          error_log("RESPONSE:\n");
          error_log($this->client->__getLastResponse() . "\n");
        }
        return $response;
      } catch (Exception $e) {
        $error = $e->detail->RpFault;
        if ($this->debug){
          error_log("REQUEST:\n");
          error_log($this->client->__getLastRequest() . "\n");
          error_log("RESPONSE:\n");
          error_log($this->client->__getLastResponse() . "\n");
        }
        return false;
      }
    }
  }
Så som du ser så tar klassen tre argument, url, certifikat och lösenord, certifikatet ska vara path:en till filen. Sedan har vi enbart två publika funktioner, sign och collect. "sign" är funktionen för att skicka iväg ett signeringsanrop till BankID, som i sin tur skapar en signeringsförfrågan till BankID-appen när den är öppen. "collect" är en funktion för att hämta status på en befintlig förfrågan. Notera hur arrayen till anropet ser annorlunda ut i de två funktionerna, något som inte är uppenbart i BankID's egna spec, detta var det jag fick lov att prova mig fram med.

Resten av bankid.php ser ut så här:

$in["socialsecurity"] = format_social_security($in["socialsecurity"]); # egen "tvätta personnummer"-funktion
  $certificate = "mittcertifikat.pem";
  $passphrase = "lösenord du angav i java-appen";
  $url = "https://appapi.bankid.com/rp/v4?wsdl";
  header("Cache-Control: no-cache, no-store");
  $bankid = new bankid($url, $certificate, $passphrase);
  #$bankid->debug = true;
  $return = array(
    "function" => $in["function"],
    "status" => $bankid->status
  );
  if ($in["function"] == "sign"){
    if ($bankid->debug) error_log($in["function"] . ": " . $in["socialsecurity"] . "\n");
    if ($response = $bankid->sign($in["socialsecurity"], "Rubrik i appen")){
      $return["reference"] = $response->orderRef;
      $return["status"] = "requested";
    } else {
      $return["status"] = "error";
    }
  }
  if ($in["function"] == "status"){
    if ($bankid->debug) error_log($in["function"] . ": " . $in["reference"] . "\n");
    if ($response = $bankid->collect($in["reference"])){
      if ($response->progressStatus == "COMPLETE"){
        # Vi är signerade! Do your stuff!
        $return["status"] = "complete";
        $return["bankid"] = $id;
      } else {
        $return["status"] = $response->progressStatus;
      }
    }
  }
  print json_encode($return);
Så beroende på vilken "function" som ditt javascript anropar med så får du olika status tillbaka.
Så första anropet ska vara "bankid.php?socialsecurity=XXXXXXXXX&function=sign"
Det skapar en ny underskriftsförfrågan till BankID för det personnumret samt returnerar "reference" vilket är det referens-id du får för den här förfrågan.
Sedan sätter du en repeat på den här förfrågan: "bankid.php?function=status&reference=XXXXXXXX" att köras varannan sekund eller så. När svaret på den förfrågan är "complete" så är signeringen klar och "Do your stuff" har sparat kunden i databasen eller whatever och i UX kan du ladda om sidan eller vad det nu är du vill göra.

Jag kör redan SSL på Apache!

Det spelar ingen roll - det här certifikatet avser bara kommunikationen mellan PHP och BankID via SOAP, och har inget med httpS-trafiken mellan din server och besökares webbläsare. Detta sker med andra ord helt i bakgrunden.
Mer i Tutorials
CSS filters for background images
PHP och BankID
Create snow in Photoshop
Modellera URLar
PHP Formatera telefonnummer
Skapa ett Netflixprogram
Expandera korta URLar
Myst Book
Selenitic Age
Ny kamera, gammal glöd
Tekoppen
Årets Halloweenfest var mycket lyckad!
Porträtt av t-o-m-u-s-a
Porträtt av u/arielgirle
Profilporträtt
GameConnect
Ny PC!
50 år
Hemma Bäst
Garageuppfart för husbil: Uppfarten är klar!
Nordic: The Musical
Livets träd
Ny kamera: Nikon Zf
Hemma Bäst
Garageuppfart för husbil: Massa grus!
The lightsabers are done!
Hemma Bäst
Garageuppfart för husbil: Lagt ut plattor och skyfflat makadam
Hemma Bäst
Garageuppfart för husbil: Grävt och klart!
Hemma Bäst
Pooltak
Nordic: The Musical
Valkyrior
Tekoppen
Kräftskiva och eldfest på Tekoppen!
Lord of the Rings timeline
Alien Timeline
Borta Bra
Snart är det Medeltidsveckan!
Hemma Bäst
Utebar: Pergola
Hemma Bäst
Utebar: Refrigerator and bar stools
Hemma Bäst
Utebar: Cupboard doors
Nordic: The Musical
Lokes Vrede
Hemma Bäst
Garageuppfart för husbil: Garageuppfart för husbil!
Arkad- och flipperkabinett
Robotar
Nordic: The Musical
Blodsbröder
Nordic: The Musical
Midgård
Nordic: The Musical
Himlen brinner
Nordic: The Musical
Orosmoln i Asgård
Nordic: The Musical
Gudarnas spel
Nordic: The Musical
Oändlig kärlek
Update on the lightsaber project
Hemma Bäst
Skåp till Ute-TV
Thåström i Globen
Bröllopspresent
Födelsedagspresent
Borta Bra
Recension: Jacy's
Thåström
Nordic: The Musical
Kunskapens pris
Nordic: The Musical
Allting börjar alltid någonstans
Borta Bra
Recension: Bohusgården Hotell & Konferens
Borta Bra
Recension: Elite Palace
New project: lightsabers as a light source
Hemma Bäst
Hemmaspa: Hemma-Spa klart!
Alien: Romulus
Eldfödd
Hemma Bäst
Utebar: The outdoor bar is ready!