PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 4.0.0
WP 2FA – Two-factor authentication for WordPress v4.0.0
4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / Admin / Methods / passkeys / class-authenticate-server.php
wp-2fa / includes / classes / Admin / Methods / passkeys Last commit date
assets 1 day ago format 1 day ago helpers 1 day ago class-ajax-passkeys.php 1 day ago class-api-register.php 1 day ago class-api-signin.php 1 day ago class-attestation-object.php 1 day ago class-authenticate-server.php 1 day ago class-authenticator-data.php 1 day ago class-byte-buffer.php 1 day ago class-chor-decoder.php 1 day ago class-passkeys-endpoints.php 1 day ago class-passkeys-user-profile.php 1 day ago class-passkeys-wizard-steps.php 1 day ago class-passkeys.php 1 day ago class-pending-2fa-helper.php 1 day ago class-source-repository.php 1 day ago class-web-authn-exception.php 1 day ago class-web-authn.php 1 day ago index.php 1 day ago
class-authenticate-server.php
184 lines
1 <?php
2 /**
3 * Responsible for the API endpoints
4 *
5 * @package wp-2fa
6 * @since 3.0.0
7 * @copyright 2026 Melapress
8 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
9 * @link https://wordpress.org/plugins/wp-2fa/
10 */
11
12 declare(strict_types=1);
13
14 namespace WP2FA\Passkeys;
15
16 use WP2FA\Methods\Passkeys\Web_Authn;
17
18 /**
19 * Authentication Server.
20 */
21 if ( ! class_exists( '\WP2FA\Passkeys\Authentication_Server' ) ) {
22
23 /**
24 * Web Authentication Server
25 *
26 * @since 3.0.0
27 */
28 class Authentication_Server {
29
30 /**
31 * Create Attestation Request for registration.
32 *
33 * @param \WP_User $user - Current User.
34 * @param string|null $challenge - Challenge string.
35 * @param bool $is_usb - Is USB authenticator.
36 *
37 * @return PublicKeyCredentialCreationOptions
38 *
39 * @since 3.0.0
40 */
41 public static function create_attestation_request( \WP_User $user, ?string $challenge = null, bool $is_usb = false ) {
42
43 // $fingerprint = base64_encode( self::generate_fingerprint() );
44
45 // $minutes = intval( 5 );
46
47 // $date = ( new \DateTime() )->add( new \DateInterval( 'PT' . $minutes . 'M' ) );
48 // $expire_date = $date->format( 'Y-m-d H:i:s' );
49
50 $challenge = base64_encode( random_bytes( 32 ) );
51
52 // Store challenge in User meta.
53 \update_user_meta( $user->ID, WP_2FA_PREFIX . 'passkey_challenge', $challenge );
54
55 $user_id = (string) \get_current_user_id();
56
57 $first_name = get_user_meta( $user_id, 'first_name', true );
58 $last_name = get_user_meta( $user_id, 'last_name', true );
59
60 $full_name = trim( $first_name . ' ' . $last_name );
61
62 if ( empty( $full_name ) ) {
63 $full_name = get_the_author_meta( 'user_email', $user_id );
64 }
65
66 $username = trim( $full_name );
67
68 $options = array(
69 'challenge' => $challenge,
70 'rp' => array(
71 'id' => Web_Authn::get_relying_party_id(),
72 'name' => Web_Authn::get_relying_party_id(),
73 ),
74 'user' => array(
75 'id' => base64_encode( $user_id ),
76 'name' => $username,
77 'displayName' => $username,
78 ),
79 'pubKeyCredParams' => array(
80 array(
81 'type' => 'public-key',
82 'alg' => -7,
83 ),
84 array(
85 'type' => 'public-key',
86 'alg' => -257,
87 ),
88 ),
89 'attestation' => 'none',
90 'extensions' => array(
91 'credProps' => true,
92 ),
93 'timeout' => intval( 5 ) * 60 * 1000,
94
95 // 'authenticatorSelection' => array(
96 // 'residentKey' => 'discouraged',
97 // 'requiredResidentKey' => false,
98 // 'userVerification' => 'required',
99 // 'authenticatorAttachment' => 'cross-platform',
100
101 // ),
102 );
103
104 if ( $is_usb ) {
105 $options['authenticatorSelection'] = array(
106 'authenticatorAttachment' => 'cross-platform',
107 'residentKey' => 'discouraged',
108 'userVerification' => 'required',
109 );
110 } else {
111 $options['authenticatorSelection'] = array(
112 'authenticatorAttachment' => 'platform',
113 'residentKey' => 'required',
114 'userVerification' => 'required',
115 );
116 }
117 // $options['authenticatorSelection'] = array(
118
119 // 'residentKey' => 'required',
120 // 'residentKey' => 'discouraged',
121 // 'userVerification' => 'required',
122 // 'authenticatorAttachment' => 'cross-platform',
123 // );
124
125 $public_key_credentials = Source_Repository::find_all_for_user( $user );
126
127 if ( ! empty( $public_key_credentials ) ) {
128 $exclude_credentials = array();
129
130 foreach ( $public_key_credentials as $public_key_credential ) {
131 $exclude_credentials[] = array(
132 'type' => 'public-key',
133 'id' => $public_key_credential['credential_id'],
134 );
135 }
136
137 $options['excludeCredentials'] = $exclude_credentials;
138 }
139
140 return $options;
141 }
142
143 /**
144 * Generates the finger print for authentication
145 *
146 * @return string
147 *
148 * @since 3.0.0
149 */
150 private static function generate_fingerprint() {
151
152 $user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? 'unknown' ) );
153 $ip_address = self::get_ip_address() ?? 'unknown';
154 $accept_language = sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'unknown' ) );
155 $accept_encoding = sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_ENCODING'] ?? 'unknown' ) );
156
157 $fingerprint_data = $user_agent . '|' . $ip_address . '|' . $accept_language . '|' . $accept_encoding;
158
159 $fingerprint_hash = hash( 'sha256', $fingerprint_data );
160
161 return $fingerprint_hash;
162 }
163
164 /**
165 * Collects the ip address of the user
166 *
167 * @return string
168 *
169 * @since 3.0.0
170 */
171 public static function get_ip_address() {
172 if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
173 $ip = \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_CLIENT_IP'] ) );
174 } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
175 $ip = \rest_is_ip_address( trim( current( preg_split( '/,/', \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) ) ) );
176 } else {
177 $ip = \sanitize_text_field( \wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) );
178 }
179
180 return (string) $ip;
181 }
182 }
183 }
184