ログイン
パスキーでログイン
簡単かつ安全
パスキーを作成
このデバイス用のパスキーを作成
通常ログイン
いつもの方法でログイン
Important points when using this code
- If using WireGuard VPN, make sure you exclude LAN traffic - otherwise, QR/cross device will not work (timesout) - ask me!
- Assumes use of CakePHP5 and PHP8, Authentication plugin (3.x)
- This demo site is 216.230.241.221 - it is Biznomos dev server. DocumentRoot is /var/www/passkey.dreamersi.net
- SVN repo URL is: svn://svn.pspinc.com/prj/cakephp5-passkey/trunk
- Use HTTPS. Passkeys won't work without it
- Create the following table and bake a model for it
CREATE TABLE `user_passkeys` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL, `credential_id` varbinary(255) NOT NULL, `public_key` text NOT NULL, `aaguid` varbinary(16) DEFAULT NULL, `sign_count` int(11) NOT NULL, `created` datetime NOT NULL, `modified` datetime NOT NULL, `last_used` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `cred_idx` (`credential_id`) ); - (Recommended) instead of copying the vendor directory in this repo, install lbuchs/webauthn like this:
cd /var/www/composer require lbuchs/webauthn - Edit your routes - add the following
$routes->scope('/passkeys', function ($routes) { $routes->connect('/login-options', ['controller' => 'Passkeys', 'action' => 'loginOptions'])->setMethods(['POST']); $routes->connect('/login-finish', ['controller' => 'Passkeys', 'action' => 'loginFinish'])->setMethods(['POST']); $routes->connect('/register-options', ['controller' => 'Passkeys', 'action' => 'registerOptions'])->setMethods(['POST']); $routes->connect('/register-finish', ['controller' => 'Passkeys', 'action' => 'registerFinish'])->setMethods(['POST']); }); $routes->scope('/', function (RouteBuilder $builder): void { // First one is optional $builder->connect('/', ['controller' => 'Users', 'action' => 'login'])->setMethods(['GET', 'POST']); // Make sure the next two matches loginUrl in src/Application.php $builder->connect('/login', ['controller' => 'Users', 'action' => 'login'])->setMethods(['GET', 'POST']); $builder->connect('/login-via-ajax', ['controller' => 'Users', 'action' => 'loginViaAjax'])->setMethods(['POST']); - Configure constants - add these to your app_local.php:
'PASSKEY_RP_NAME' => 'Name of your service', 'PASSKEY_RP_ID' => 'foobar.dreamersi.net', 'PASSKEY_RP_ORIGIN' => 'https://foobar.dreamersi.net' - In your src/Application.php, allow multiple login URLs like this:
$authenticationService->loadAuthenticator('Authentication.Form', [ 'fields' => [ 'username' => 'email', 'password' => 'password', ], 'loginUrl' => ['/login', '/login-via-ajax'] ]); - In your layout file, set the CSRF token like this (it is read by JavaScript)
echo $this->Html->meta('csrfToken', $this->request->getAttribute('csrfToken')); - Copy the JS files - they are in webroot/js/passkeys
- Copy the PasskeysController.php and use it in your code
- Copy the loginViaAjax action in UsersController.php and place it in your UsersController.php
- (For PHP7.4 only) Add this to your config/bootstrap.php - it adds a function missing in PHP7.4 that is required by webauthn
// Polyphil - for PHP7.4 if (!function_exists('str_starts_with')) { function str_starts_with($haystack, $needle) { return strncmp($haystack, $needle, strlen($needle)) === 0; } } - Copy passkeys.po in resources/locales/ja_JP and en_US to your folder - these are required to translate from aaguid to provider names
- In your UsersController.php, exempt loginViaAjax from form protection like this:
public function beforeFilter(\Cake\Event\EventInterface $event) { parent::beforeFilter($event); $this->Authentication->addUnauthenticatedActions(['login', 'logout', 'loginViaAjax']); $this->FormProtection->setConfig('unlockedActions', [ 'loginViaAjax', ]); } - Note: credential_id and aaguid are VARBINARY and will be hydrated as stream resources.
Use stream_get_contents() when needed.
$aaguidBinary = stream_get_contents($passkey->aaguid); $aaguidHex = bin2hex($aaguidBinary); $deviceType = strtolower($aaguidHex); // Then you can load SVG images like this - see Users/manage_passkeys.php $imagePath = '/img/passkeys/' . $deviceType . '.svg'; // Also get the provider name (Google, Apple etc) like this: $label = __d('passkeys', $deviceType); // actual names are stored in translation files