22
33/**
44* SimpleSAMLphp WordpressAuth
5- * Version 0.1 .0
5+ * Version 0.2 .0
66*
77* SimpleSAMLphp module to use Wordpress as a SAML 2.0 Identity Provider.
88*
99* WordpressAuth is a SimpleSAMLphp authentication module, that allows to use
1010* the Wordpress user database as the authentication source. The code was written
1111* for MySQL/MariaDB.
12- *
13- *
12+ *
13+ *
1414* Documentation: https://github.com/disisto/simplesamlphp-wordpressauth
15- *
15+ *
1616* Forked from https://github.com/OliverMaerz/WordpressAuth
1717* forked from https://github.com/Financial-Edge/simplesamlphp-module-wordpressauth/
1818*
1919* Licensed under GNU GPL v2.0 (https://github.com/disisto/simplesamlphp-wordpressauth/blob/master/LICENSE)
2020*
21- * Copyright (c) 2023 Roberto Di Sisto
21+ * Copyright (c) 2023-2025 Roberto Di Sisto
2222*
2323* Permission is hereby granted, free of charge, to any person obtaining a copy
2424* of this software and associated documentation files (the "Software"), to deal
2929*
3030* The above copyright notice and this permission notice shall be included in all
3131* copies or substantial portions of the Software.
32- *
32+ *
3333* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3434* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3535* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3636* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3737* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3838* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3939* SOFTWARE.
40+ *
4041**/
4142
4243namespace SimpleSAML \Module \wordpressauth \Auth \Source ;
@@ -76,6 +77,56 @@ public function __construct($info, $config) {
7677 $ this ->password = $ config ['password ' ];
7778 }
7879
80+ /**
81+ * Verify password against WordPress hash
82+ * Supports:
83+ * - WordPress 6.8+ BCrypt with $wp$ prefix (uses HMAC-SHA384 + Base64 before verification)
84+ * - Standard BCrypt hashes: $2y$, $2a$, $2b$
85+ * - Legacy phpass hashes: $P$, $H$
86+ *
87+ * @param string $password The plaintext password
88+ * @param string $stored_hash The hash from database
89+ * @return bool True if password matches
90+ */
91+ private function verifyPassword (string $ password , string $ stored_hash ): bool {
92+ // WordPress 6.8+ prefixed BCrypt hash
93+ if (substr ($ stored_hash , 0 , 4 ) === '$wp$ ' ) {
94+ Logger::debug ('WordpressAuth: Detected $wp$ prefix, using WordPress 6.8+ HMAC-SHA384 method ' );
95+
96+ // WordPress 6.8 uses HMAC-SHA384 + Base64 encoding before BCrypt verification
97+ $ password_to_verify = base64_encode (hash_hmac ('sha384 ' , $ password , 'wp-sha384 ' , true ));
98+
99+ // Strip only $wp but keep the $ prefix (substr 3, not 4)
100+ $ hash = substr ($ stored_hash , 3 );
101+
102+ Logger::debug ('WordpressAuth: Verifying HMAC-SHA384(password) against BCrypt hash ' );
103+ return password_verify ($ password_to_verify , $ hash );
104+ }
105+
106+ // Standard BCrypt hashes ($2y$, $2a$, $2b$) without $wp$ prefix
107+ $ bcrypt_prefixes = ['$2y$ ' , '$2a$ ' , '$2b$ ' ];
108+ foreach ($ bcrypt_prefixes as $ prefix ) {
109+ if (substr ($ stored_hash , 0 , 4 ) === $ prefix ) {
110+ Logger::debug ('WordpressAuth: Using password_verify() for standard BCrypt hash ' );
111+ return password_verify ($ password , $ stored_hash );
112+ }
113+ }
114+
115+ // Legacy phpass hashes ($P$, $H$)
116+ $ phpass_prefixes = ['$P$ ' , '$H$ ' ];
117+ foreach ($ phpass_prefixes as $ prefix ) {
118+ if (substr ($ stored_hash , 0 , 3 ) === $ prefix ) {
119+ Logger::debug ('WordpressAuth: Using PasswordHash for legacy phpass hash ' );
120+ $ hasher = new PasswordHash (8 , TRUE );
121+ return $ hasher ->CheckPassword ($ password , $ stored_hash );
122+ }
123+ }
124+
125+ // Unknown hash format
126+ Logger::warning ('WordpressAuth: Unknown password hash format: ' . substr ($ stored_hash , 0 , 10 ) . '... ' );
127+ return false ;
128+ }
129+
79130 protected function login (string $ username , string $ password ): array {
80131 // Connect to the database
81132 $ db = new PDO ($ this ->dsn , $ this ->username , $ this ->password );
@@ -135,23 +186,27 @@ protected function login(string $username, string $password): array {
135186 throw new Error ('WRONGUSERPASS ' );
136187 }
137188
138- $ hasher = new PasswordHash (8 , TRUE );
139-
140- // Check the password against the hash in Wordpress wp_users table
141- if (!$ hasher ->CheckPassword ($ password , $ row ['user_pass ' ])){
189+ // Verify password using the enhanced method
190+ if (!$ this ->verifyPassword ($ password , $ row ['user_pass ' ])) {
142191 // Invalid password
192+ Logger::info ('WordpressAuth: Invalid password for user: ' . $ username );
143193 throw new Error ('WRONGUSERPASS ' );
144194 }
145195
146- // Define the meta keys in an array
147- $ meta_keys = [
196+ Logger::info ('WordpressAuth: Successful login for user: ' . $ username );
197+
198+ // Define the meta keys in an array
199+ // Show keys even if values are empty/null
200+ $ meta_keys = [
148201 'first_name ' ,
149202 'last_name ' ,
150- 'profile_photo ' ,
151- $ table_prefix .'capabilities '
203+ 'profile_photo ' , // Plugin: Ultimate Member (https://wordpress.org/plugins/ultimate-member/)
204+ 'scopes ' // Plugin: Extra User Details (https://wordpress.org/plugins/extra-user-details/)
205+ //$table_prefix.'capabilities'
152206 // ... add more keys as needed
153207 ];
154208
209+
155210 // Fetch meta_keys from wp_usermeta table defined above
156211 $ meta_keys_placeholders = implode ("', ' " , $ meta_keys );
157212 $ meta_sql = "SELECT meta_key, meta_value FROM " .$ table_prefix ."usermeta WHERE user_id = :id AND meta_key IN (' $ meta_keys_placeholders') " ;
@@ -171,15 +226,16 @@ protected function login(string $username, string $password): array {
171226 ];
172227
173228 foreach ($ meta_rows as $ meta_row ) {
174- $ meta_key = $ meta_row ['meta_key ' ];
175- $ meta_value = $ meta_row ['meta_value ' ];
229+ $ meta_key = $ meta_row ['meta_key ' ];
230+ $ meta_value = $ meta_row ['meta_value ' ];
176231
177- if (in_array ($ meta_key , $ meta_keys )) {
178- $ attribute_name = str_replace ($ table_prefix , '' , $ meta_key );
179- $ attributes [$ attribute_name ] = [$ meta_value ];
180- }
232+ if (in_array ($ meta_key , $ meta_keys )) {
233+ $ attribute_name = str_replace ($ table_prefix , '' , $ meta_key );
234+ $ attributes [$ attribute_name ] = [$ meta_value ];
235+ }
181236 }
182237
238+
183239 // Return the attributes
184240 return $ attributes ;
185241 }
0 commit comments