joshcanhelp / wp-rest-api-auth0

Authorize WP REST API calls with access tokens from Auth0
MIT License
5 stars 1 forks source link

auth0_id missing on new WP users' meta data #11

Closed EgilSandfeld closed 2 years ago

EgilSandfeld commented 2 years ago

I'm a bit at a loss here and hoping you can lead me in the right direction. After having a successful Auth0 WordPress connection up and running, I've used your article to verify users' subscription from the WP REST. I've taken the code from wp-rest-api-auth.php and pasted it into a PHP snippet on my WP admin. Minor changes and additions here:

` $wp_user = reset( get_users( array( 'meta_key' => $meta_key, 'meta_value' => $decoded_token['sub'], 'number' => 1 )
) );

// Could not find a user with the incoming Auth0 ID.
if ( ! $wp_user ) {
    echo "No wp_user found with sub: " . $decoded_token['sub'] . " and meta_key: " . $meta_key;
    return null;
}`

This part fails with a valid $decoded_token['sub'] and meta key, and it doesn't return any users:

"No wp_user found with sub: google-oauth2|1098..........8889 and meta_key: wp_DREauth0_id {\"code\":\"rest_forbidden\",\"message\":\"Sorry, you are not allowed to do that.\",\"data\":{\"status\":401}}"

The real issue here is probably not that code part, but the fact that new users do not have the auth0_id added to their user metadata. Looking at the WP users, it's clear that at some point they did get auth0_id added, but not anymore:

image

I might previously have added these auth0_ids manually (I can't honestly remember), so it might be that this never worked in the first place. I can confirm that all users have the user_id present in Auth0 User Management. Example: image

What should I do to (re)establish auth0_id presence in WP users meta data?

joshcanhelp commented 2 years ago

Thanks so much for the feedback and questions here! I'll take a look as soon as I'm able.

EgilSandfeld commented 2 years ago

Looking more into it, the response content from Auth0.com/userinfo yields the following:

{
  "sub":"google-oauth2|1098...8889",
  "given_name":"AWE",
  "family_name":"Tester",
  "nickname":"theawetesting",
  "name":"AWE Tester",
  "picture":"https://lh3.googleusercontent.com/a/AATXAJw...sY9t=s96-c",
  "locale":"en",
  "updated_at":"2022-03-30T07:07:09.835Z",
  "email":"....@gmail.com",
  "email_verified":true,
  "https://custom-claim/has_wp_account":true
}

So the https://custom-claim/has_wp_account is set to true here, so far so good, but on the other existing WP users this property is not even present in the response?!

Anyway, I believe I made a workaround to solve my issue, by adding this to the determine_current_user filter:

// Could not find a user with the incoming Auth0 ID.
if ( ! $wp_user ) {
  $wp_user = get_user_by_email($decoded_token['email']);

  // Could not find a user by the incoming email either.
  if ( ! $wp_user ) {
      echo " No wp_user found by sub: " . $decoded_token['sub'] . " or found by email: " . $decoded_token['email'];
      return null;
  }

  //Found the user by email, so set the meta data auth0_id value using $decoded_token['sub']
  //echo " Found by email: " . $decoded_token['email'] . " and adding sub: " . $decoded_token['sub'];
  update_user_meta( $wp_user->ID, $meta_key, $decoded_token['sub']);
}
  1. This check as usual for existing auth0_id user meta data
  2. If it finds it, it continues as usual
  3. If it doesn't find it, it checks for user by email
  4. If that isn't found either it returns null
  5. If user by email is found, it adds the auth0_id to that user and continues

I would say this is still quite safe to do, as it still requires both a known auth0_id + known WP user email. Let me know if this is bad practice.

EgilSandfeld commented 2 years ago

Okay, my question is shifting slightly towards the WP-Auth0 setup now. While the above works for adding the auth0_id to an existing WP user, my setup isn't sufficient for new users being created via an app.

I have two apps in Auth0:

1) Regular Web Application, which uses Token Endpoint Auth Method: POST (users should be able to login/signup via the website) 2) Native C# App, which uses Token Endpoint Auth Method: NONE (users should be able to login/signup via the app) Both are connected to the same DBs and authentication ways, but the native apps maybe can't access the WP-API I made for the Regular Web variant.

I think this is due to the WP Auth0 plugin only being connected with the 1) Regular Web Application or that the Native App isn't connected properly with the WP-API during user creation?

How can a WP-User be created using both a native app and regular web app?

joshcanhelp commented 2 years ago

@EgilSandfeld - Apologies for the delay in response here.

For your first comment about linking the incoming user in WordPress ... I have not worked in the plugin for a while but, last I was in there, it would link the user by default if an email is found but only if the incoming user has an email_verified claim set to true (which your example user does). The reason for this is that a user could register themselves in Auth0 under any email address they want and, if it's not verified, they would be linked to an account in WordPress without any checks. Now they would be able to log into the WordPress account using their Auth0 credentials without any check on whether they own that email address or not.

For your next comment ... the native app should be able to make an account on Auth0, that's fine. The Rule code provided in the post only checks for the existence of an account, it does not create an account. If your native app sees that claim is false then it needs to send the user to the WordPress site to register (which will use the same Auth0 user).

Hope that helps a bit and apologies again for the long delay!

EgilSandfeld commented 2 years ago

Thank you for the help and push in the right direction. I have it working now, where it's able to create a WP user from the Auth0 data. I basically took most of your code and adapted it to create the WP user:

`declare(strict_types=1);

namespace DRE\WordPress\RestApiAuth0;

header('Content-Type: application/json');

function create_wpuser($data) {

// Only checked, not saved or output anywhere.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
$request_uri = wp_unslash( $_SERVER['REQUEST_URI'] ?? '' );

// Validated below with TokenVerifier.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
$auth_header_raw = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? '';
$auth_header_raw = wp_unslash( $auth_header_raw );

// Check if this is a WP REST API request.
if ( 1 !== preg_match( '/^\/' . rest_get_url_prefix() . '(.*)/', $request_uri ) ) {
    return $data;
}

// Check for a token in the Authorization header.
$auth_header_parts = explode( ' ', $auth_header_raw );
if ( 'Bearer' !== $auth_header_parts[0] || empty( $auth_header_parts[1] ) ) {
    return $data;
}

// From this point on, we're going to treat the request as OAuth2 protected.
// If we cannot validate the token for some reason, the request is processed without auth.

if (empty($_GET['scope'])) {
    die( 'No `scope` URL parameter' );
}

if (empty($_GET['id_token'])) {
    die( 'No `id_token` URL parameter' );
}

if (empty($_GET['token_alg']) || ! in_array($_GET['token_alg'], [ 'HS256', 'RS256' ])) {
    die( 'Missing or invalid `token_alg` URL parameter' );
}

$token_issuer  = 'https://'.AUTH0_DOMAIN.'/';
$client_id  = AUTH0_CLIENT_ID;
$id_token  = rawurldecode($_GET['id_token']);
$signature_verifier = null;

if ('RS256' === $_GET['token_alg']) {
    $jwks_fetcher = new \WP_Auth0_JwksFetcher();
    $jwks        = $jwks_fetcher->getKeys($token_issuer.'.well-known/jwks.json');
    $signature_verifier = new \WP_Auth0_AsymmetricVerifier($jwks_fetcher);
} else if ('HS256' === $_GET['token_alg']) {
    $signature_verifier = new WP_Auth0_AsymmetricVerifier(getenv('AUTH0_CLIENT_SECRET'));
}

$token_verifier = new \WP_Auth0_IdTokenVerifier(
    $token_issuer,
    $client_id,
    $signature_verifier
);

try {
    $decoded_token = $token_verifier->verify($id_token);
} catch (\Exception $e) {
    echo 'Caught: Exception - '.$e->getMessage();
}

// We don't have a user to associate this call to.
if ( ! $decoded_token['sub'] ) {
    return null;
}

global $wpdb;
$meta_key = $wpdb->prefix . 'auth0_id';
$username = $decoded_token['https://custom-claim/username'];

$user_id = wp_insert_user( array(
  'user_login' => $username,
  'user_pass' => '',
  'user_email' => $decoded_token['email'],
  'first_name' => $decoded_token['email'] . ' ' . $decoded_token['nickname'],
  'display_name' => $username,
  'role' => 'subscriber'
));

if ( is_wp_error( $user_id ) ) {
    // something went wrong
    echo $user_id->get_error_message();
    return null;
}

$wp_user = get_user_by_email($decoded_token['email']);

if ( ! $wp_user ) {
    echo " User not created: " . $decoded_token['email'];
    return null;
}

//Found the user by email, so set the meta data auth0_id value using $decoded_token['sub']
update_user_meta( $wp_user->ID, $meta_key, $decoded_token['sub']);

// Pull the scopes out of the access token and adjust the user accordingly.
$access_token_scopes = explode( ' ', $_GET['scope'] );
foreach ( $wp_user->allcaps as $cap => $value ) {
    if ( ! in_array( $cap, $access_token_scopes, true ) ) {
        $wp_user->allcaps[ $cap ] = 0;
    }
}

// Set the current user as this modified user.
//global $current_user;
//$current_user = $wp_user;
return $wp_user->ID;

}`

add_action( 'rest_api_init', function () { register_rest_route( 'dre/v1', '/create_user/', array( 'methods' => 'GET', 'callback' => __NAMESPACE__ . '\\create_wpuser', 'permission_callback' => '__return_true' ) ); } );

I've left the WP user password empty, which seems to be working. Since there's IdentityToken brought along, a password seems redundant to set, right?

Also, I'm not sure whether to uncomment the last bit:

// Set the current user as this modified user. //global $current_user; //$current_user = $wp_user;

joshcanhelp commented 2 years ago

If there's anything that should be changed for the general case, a PR would be appreciated! I

As for the commented code ... I don't know if I can say whether that's necessary or not without understanding exactly what you're doing here. Those two lines set the session to whatever $wp_user is set to, effectively logging them in as that user. Since you could set that to literally anything, it's a critical thing to get right!

EgilSandfeld commented 2 years ago

Don't think any PR is necessary for this scenario. Thanks for taking the time to help me 🤘