firehed/u2f
最新稳定版本:1.2.0
Composer 安装命令:
composer require firehed/u2f
包简介
A library providing U2F authentication
关键字:
README 文档
README
This repository has been replaced by firehed/webauthn-php, and is no longer being maintained. The replacement no longer supports the long-deprecated U2F protocol, which allows for a more modern and flexible API. It DOES support U2F hardware keys, but only through the WebAuthn protocols.
U2F
A PHP implementation of the FIDO U2F authentication standard. Now also for Web Authentication!
Introduction
Web Authenication (commonly called WebAuthn) is a set of technologies to securely authenticate users in web applications. It is most commonly used as a second factor - either biometrics or a hardware device - to supplement password logins. It allows websites to replace the need for a companion app (such as Google Authenticator) or communication protocols (e.g. SMS) with a hardware-based second factor.
This library has its roots in the U2F (universal second factor) protocol that WebAuthn evolved from, and supports both standards. Note that browsers are starting to drop support for the original U2F protocols in favor of WebAuthn; consequently, this library will do the same in the next major version.
This library is designed to allow easy integration of the U2F protocol to an existing user authentication scheme. It handles the parsing and validating all of the raw message formats, and translates them into standard PHP objects.
Note that use of the word "key" throughout this document should be interpreted to mean "FIDO U2F Token". These are often USB "keys" but can also be NFC or Bluetooth devices.
There are two main operations that you will need to understand for a successful integration: registration and authentication. Registration is the act of associating a key that the end-user is physically in posession of with their existing account; authentication is where that key is used to cryptographically sign a message from your application to verify posession of said key.
Additional resources:
Demo
You may try all of this at https://u2f.ericstern.com, and see the corresponding code at https://github.com/Firehed/u2f-php-examples
The example code is only designed to show how the APIs interact with each other, and intentionally leaves out best practices such as use of routers and dependency inversion containers to keep the examples as simple as possible. See its README for more information.
Installation
composer require firehed/u2f
Note: you must not be using the deprecated mbstring.func_overload functionality, which can completely break working on binary data.
The library will immediately throw an exception if you have it enabled.
Usage
Usage will be described in three parts: setup, registration, and authentication. The code in setup should be used before both registration and authentication.
The API is designed to "fail loudly"; that is, failures will throw an exception, ensuring that return values are always the result of a successful operation.
This reduces the need for complex error checking and handling during use, since the whole thing can be simply wrapped in a try/catch block and assume that everything went well if no exceptions are caught.
This guide covers the modern Web Authentication ("WebAuthn") usage and data formats. More information on the legacy U2F protocols are available in versions of this README from v1.1.0 and earlier.
Setup
All operations are performed by the U2F Server class, so it needs to be instanciated and configured:
use Firehed\U2F\Server; $server = new Server('u2f.example.com'); $server->setTrustedCAs(glob('path/to/certs/*.pem'));
The trusted CAs are whitelisted vendors, and must be an array of absolute paths to PEM-formatted CA certs (as strings).
Some provider certificates are provided in the CACerts/ directory in the repository root; in a deployed project, these should be available via $PROJECT_ROOT/vendor/firehed/u2f/CACerts/*.pem.
You may also choose to disable CA verification, by calling ->disableCAVerification() instead of setTrustedCAs().
This removes trust in the hardware vendors, but ensures that as new vendors issue tokens, they will be forward-compatible with your website.
The URI provided to the constructor must be the HTTPS domain component of your website. See FIDO U2F AppID and Facet Specification for additional information.
Registration
Registering a token to a user's account is a two-step process: generating a challenge, and verifying the response to that challenge.
Generating the challenge
Start by generating a challenge. You will need to store this temporarily (e.g. in a session), then send it to the user:
$challenge = $server->generateChallenge(); $_SESSION['registration_challenge'] = $challenge; header('Content-type: application/json'); echo json_encode($challenge);
Client-side registration
Create a PublicKeyCredentialCreationOptions data structure, and provide it to the WebAuthn API:
const userId = "some value from your application" const challenge = "challenge string from above" const options = { rp: { name: "Example Site", }, user: { id: Uint8Array.from(userId, c => c.charCodeAt(0)), name: "user@example.com", displayName: "User Name", }, challenge: Uint8Array.from(challenge, c => c.charCodeAt(0)), pubKeyCredParams: [{alg: -7, type: "public-key"}], timeout: 60000, // 60 seconds authenticatorSelection: { authenticatorAttachment: "cross-platform", userVerification: "preferred", }, attestation: "direct" } // If the user completes registration, this value will hold the data to POST to your application const credential = await navigator.credentials.create({ publicKey: options }) // Format the user's `credential` and POST it to your application: const dataToSend = { rawId: new Uint8Array(credential.rawId), type: credential.type, response: { attestationObject: new Uint8Array(credential.response.attestationObject), clientDataJSON: new Uint8Array(credential.response.clientDataJSON), }, } // Pseudocode: // POST will send JSON.stringify(dataToSend) with an application/json Content-type header const response = POST('/verifyRegisterChallenge.php', dataToSend)
Parse and verify the response
Using the previously-generated registration request, ask the server to verify the POSTed data. If verification succeeds, you will have a Registration object to associate with the user:
// You should validate that the inbound request has an 'application/json' Content-type header $rawPostBody = trim(file_get_contents('php://input')); $data = json_decode($rawPostBody, true); $response = \Firehed\U2F\WebAuthn\RegistrationResponse::fromDecodedJson($data); $challenge = $_SESSION['registration_challenge']; $registration = $server->validateRegistration($challenge, $response);
Persist the $registration
Registrations SHOULD be persisted as a one-to-many relationship with the user, since a user may own multiple keys and may want to associate all of them with their account (e.g. a backup key is kept on a spouse's keychain).
It is RECOMMENDED to use (user_id, key_handle) as a unique composite identifier.
-- A schema with roughly this format is ideal CREATE TABLE token_registrations ( id INTEGER PRIMARY KEY, user_id INTEGER, counter INTEGER, key_handle TEXT, public_key TEXT, attestation_certificate TEXT, FOREIGN KEY (user_id) REFERENCES users(id), UNIQUE(user_id, key_handle) )
// This assumes you are connecting to your database with PDO $query = <<<SQL INSERT INTO token_registrations ( user_id, counter, key_handle, public_key, attestation_certificate ) VALUES ( :user_id, :counter, :key_handle, :public_key, :attestation_certificate ) SQL; $stmt = $pdo->prepare($query); // Note: you may want to base64- or hex-encode the binary values below. // Doing so is entirely optional. $stmt->execute([ ':user_id' => $_SESSION['user_id'], ':counter' => $registration->getCounter(), ':key_handle' => $registration->getKeyHandleBinary(), ':public_key' => $registration->getPublicKey()->getBinary(), ':attestation_certificate' => $registration->getAttestationCertificate()->getBinary(), ]);
After doing this, you should add a flag of some kind on the user to indicate that 2FA is enabled, and ensure that they have authenticated with their second factor. Since this is entirely application-specific, it won't be covered here.
Authentication
Authentication is a similar process as registration: generate challenges to sign for each of the user's registrations, and validate the response when received.
Generating the challenge
Start by generating sign requests. Like with registration, you will need to store them temporarily for verification. After doing so, send them to the user:
$registrations = $user->getU2FRegistrations(); // this must be an array of Registration objects $challenge = $server->generateChallenge(); $_SESSION['login_challenge'] = $challenge; // WebAuthn expects a single challenge for all key handles, and the Server generates the requests accordingly. header('Content-type: application/json'); echo json_encode([ 'challenge' => $challenge, 'key_handles' => array_map(function (\Firehed\U2F\RegistrationInterface $reg) { return $reg->getKeyHandleWeb(); }, $registrations), ]);
Client-side authentication
Create a PublicKeyCredentialRequestOptions data structure, and provide it to the WebAuthn API:
// This is a basic decoder for the above `getKeyHandleWeb()` format const fromBase64Web = s => atob(s.replace(/\-/g,'+').replace(/_/g,'/')) // postedData is the decoded JSON from the above snippet const challenge = postedData.challenge const keyHandles = postedData.key_handles const options = { challenge: Uint8Array.from(challenge, c => c.charCodeAt(0)), allowCredentials: keyHandles.map(kh => ({ id: Uint8Array.from(fromBase64Web(kh), c => c.charCodeAt(0)), type: 'public-key', transports: ['usb', 'ble', 'nfc'], })), timeout: 60000, } // If the user authenticates, this value will hold the data to POST to your application const assertion = await navigator.credentials.get({ publicKey: options }); // Format the user's `assertion` and POST it to your application: const dataToSend = { rawId: new Uint8Array(assertion.rawId), type: assertion.type, response: { authenticatorData: new Uint8Array(assertion.response.authenticatorData), clientDataJSON: new Uint8Array(assertion.response.clientDataJSON), signature: new Uint8Array(assertion.response.signature), }, } // Pseudocode, same as above const response = await POST('/verifyLoginChallenge.php', dataToSend)
Parse and verify the response
Parse the POSTed data into a LoginResponseInterface:
// You should validate that the inbound request has an 'application/json' Content-type header $rawPostBody = trim(file_get_contents('php://input')); $data = json_decode($rawPostBody, true); $response = \Firehed\U2F\WebAuthn\LoginResponse::fromDecodedJson($data); $registrations = $user->getU2FRegistrations(); // Registration[] $registration = $server->validateLogin( $_SESSION['login_challenge'], $response, $registrations );
Persist the updated $registration
If no exception is thrown, $registration will be a Registration object with an updated counter; you MUST persist this updated counter to wherever the registrations are stored.
Failure to do so is insecure and exposes your application to token cloning attacks.
// Again, assumes a PDO connection $query = <<<SQL UPDATE token_registrations SET counter = :counter WHERE user_id = :user_id AND key_handle = :key_handle SQL; $stmt = $pdo->prepare($query); $stmt->execute([ ':counter' => $registration->getCounter(), ':user_id' => $_SESSION['user_id'], ':key_handle' => $registration->getKeyHandleBinary(), // if you are storing base64- or hex- encoded above, do so here as well ]);
If you reach this point, the user has succcessfully authenticated with their second factor. Update their session to indicate this, and allow them to proceeed.
Tests
All tests are in the tests/ directory and can be run with vendor/bin/phpunit.
License
MIT
firehed/u2f 适用场景与选型建议
firehed/u2f 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 4.88k 次下载、GitHub Stars 达 73, 最近一次更新时间为 2018 年 01 月 16 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「security」 「Authentication」 「auth」 「yubikey」 「yubico」 「MFA」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 firehed/u2f 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 firehed/u2f 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 firehed/u2f 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Laravel Multiauth package
Automatically logs-in users if they are already authenticated by a remote source. (e.g. environment variable REMOTE_USER)
GraphQL authentication for your headless Craft CMS applications.
A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.
Provide a way to secure accesses to all routes of an symfony application.
Laravel middleware to restrict a site or specific routes using HTTP basic authentication
统计信息
- 总下载量: 4.88k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 74
- 点击次数: 28
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2018-01-16