getSelectedTanMode()->isDecoupled()) { handleDecoupled($action); } else { handleTan($action); } } /** * This function handles strong authentication for the case where the user needs to enter a TAN into the PHP * application. * * @param \Fhp\BaseAction $action Some action that requires a TAN. * @throws CurlException|UnexpectedResponseException|ServerException See {@link FinTs::execute()} for details. */ function handleTan(Fhp\BaseAction $action) { global $fints, $options, $credentials; // Find out what sort of TAN we need, tell the user about it. $tanRequest = $action->getTanRequest(); echo 'The bank requested a TAN.'; if ($tanRequest->getChallenge() !== null) { echo ' Instructions: ' . $tanRequest->getChallenge(); } echo "\n"; if ($tanRequest->getTanMediumName() !== null) { echo 'Please use this device: ' . $tanRequest->getTanMediumName() . "\n"; } // Challenge Image for PhotoTan/ChipTan if ($tanRequest->getChallengeHhdUc()) { try { $flicker = new \Fhp\Model\FlickerTan\TanRequestChallengeFlicker($tanRequest->getChallengeHhdUc()); echo 'There is a challenge flicker.' . PHP_EOL; // save or output svg $flickerPattern = $flicker->getFlickerPattern(); // other renderers can be implemented with this pattern $svg = new \Fhp\Model\FlickerTan\SvgRenderer($flickerPattern); echo $svg->getImage(); } catch (InvalidArgumentException $e) { // was not a flicker $challengeImage = new \Fhp\Model\TanRequestChallengeImage( $tanRequest->getChallengeHhdUc() ); echo 'There is a challenge image.' . PHP_EOL; // Save the challenge image somewhere // Alternative: HTML sample code echo '' . PHP_EOL; } } // Optional: Instead of printing the above to the console, you can relay the information (challenge and TAN medium) // to the user in any other way (through your REST API, a push notification, ...). If waiting for the TAN requires // you to interrupt this PHP process and the TAN will arrive in a fresh (HTTP/REST/...) request, you can do so: if ($optionallyPersistEverything = false) { $persistedAction = serialize($action); $persistedFints = $fints->persist(); // These are two strings (watch out, they are NOT necessarily UTF-8 encoded), which you can store anywhere. // This example code stores them in a text file, but you might write them to your database (use a BLOB, not a // CHAR/TEXT field to allow for arbitrary encoding) or in some other storage (possibly base64-encoded to make it // ASCII). file_put_contents(__DIR__ . '/state.txt', serialize([$persistedFints, $persistedAction])); } // Ask the user for the TAN. ---------------------------------------------------------------------------------------- // IMPORTANT: In your real application, you cannot use fgets(STDIN) of course (unless you're running PHP only as a // command line application). So you instead want to send a response to the user. This means that, after executing // the first half of handleTan() above, your real application will terminate the PHP process. The second half of // handleTan() will likely live elsewhere in your application code (i.e. you will have two functions for the TAN // handling, not just one like in this simplified example). You *only* need to carry over the $persistedInstance // and the $persistedAction (which are simple strings) by storing them in some database or file where you can load // them again in a new PHP process when the user sends the TAN. echo "Please enter the TAN:\n"; $tan = trim(fgets(STDIN)); // Optional: If the state was persisted above, we can restore it now (imagine this is a new PHP process). if ($optionallyPersistEverything) { $restoredState = file_get_contents(__DIR__ . '/state.txt'); list($persistedInstance, $persistedAction) = unserialize($restoredState); $fints = \Fhp\FinTs::new($options, $credentials, $persistedInstance); $action = unserialize($persistedAction); } echo "Submitting TAN: $tan\n"; $fints->submitTan($action, $tan); } /** * This function handles strong authentication for the case where the user needs to confirm the action on another * device. Note: Depending on the banks you need compatibility with, you may not need to implement decoupled * authentication at all, i.e., you could filter out any decoupled TanModes when letting the user choose. * * @param \Fhp\BaseAction $action Some action that requires decoupled authentication. * @throws CurlException|UnexpectedResponseException|ServerException See {@link FinTs::execute()} for details. */ function handleDecoupled(Fhp\BaseAction $action) { global $fints, $options, $credentials; $tanMode = $fints->getSelectedTanMode(); $tanRequest = $action->getTanRequest(); echo 'The bank requested authentication on another device.'; if ($tanRequest->getChallenge() !== null) { echo ' Instructions: ' . $tanRequest->getChallenge(); } echo "\n"; if ($tanRequest->getTanMediumName() !== null) { echo 'Please check this device: ' . $tanRequest->getTanMediumName() . "\n"; } // Just like in handleTan() above, we have the option to interrupt the PHP process at this point. In fact, the // for-loop below that deals with the polling may even be running on the client side of your larger application, // polling your application server regularly, which spawns a new PHP process each time. Here, we demonstrate this by // persisting the instance to a local file and restoring it (even though that's not technically necessary for a // single-process CLI script like this). if ($optionallyPersistEverything = false) { $persistedAction = serialize($action); $persistedFints = $fints->persist(); // See handleTan() for how to deal with this in practice. file_put_contents(__DIR__ . '/state.txt', serialize([$persistedFints, $persistedAction])); } // IMPORTANT: In your real application, you don't have to use sleep() in PHP. You can persist the state in the same // way as in handleTan() and restore it later. This allows you to use some other timer mechanism (e.g. in the user's // browser). This PHP sample code just serves to show the *logic* of the polling. Alternatively, you can even do // without polling entirely and just let the user confirm manually in all cases (i.e. only implement the `else` // branch below). if ($tanMode->allowsAutomatedPolling()) { echo "Polling server to detect when the decoupled authentication is complete.\n"; sleep($tanMode->getFirstDecoupledCheckDelaySeconds()); for ($attempt = 0; $tanMode->getMaxDecoupledChecks() === 0 || $attempt < $tanMode->getMaxDecoupledChecks(); ++$attempt ) { // Optional: If the state was persisted above, we can restore it now (imagine this is a new PHP process). if ($optionallyPersistEverything) { $restoredState = file_get_contents(__DIR__ . '/state.txt'); list($persistedInstance, $persistedAction) = unserialize($restoredState); $fints = \Fhp\FinTs::new($options, $credentials, $persistedInstance); $action = unserialize($persistedAction); } // Check if we're done. if ($fints->checkDecoupledSubmission($action)) { echo "Confirmed.\n"; return; } echo "Still waiting...\n"; // THIS IS CRUCIAL if you're using persistence in between polls. You must re-persist() the instance after // calling checkDecoupledSubmission() and before calling it the next time. Don't reuse the // $persistedInstance from above multiple times. if ($optionallyPersistEverything) { $persistedAction = serialize($action); $persistedFints = $fints->persist(); file_put_contents(__DIR__ . '/state.txt', serialize([$persistedFints, $persistedAction])); } sleep($tanMode->getPeriodicDecoupledCheckDelaySeconds()); } throw new RuntimeException("Not confirmed after $attempt attempts, which is the limit."); } elseif ($tanMode->allowsManualConfirmation()) { echo "Please type 'done' and hit Return when you've completed the authentication on the other device.\n"; while (trim(fgets(STDIN)) !== 'done') { echo "Try again.\n"; } echo "Confirming that the action is done.\n"; if (!$fints->checkDecoupledSubmission($action)) { throw new RuntimeException( "You confirmed that the authentication for action was copmleted, but the server does not think so." ); } echo "Confirmed\n"; } else { throw new AssertionError('Server allows neither automated polling nor manual confirmation'); } } // Select TAN mode and possibly medium. If you're not sure what this is about, read and run tanModesAndMedia.php first. $tanMode = 900; // This is just a placeholder you need to fill! $tanMedium = null; // This is just a placeholder you may need to fill. $fints->selectTanMode($tanMode, $tanMedium); // Log in. $login = $fints->login(); if ($login->needsTan()) { handleStrongAuthentication($login); } // Usage: // $fints = require_once 'login.php'; return $fints;