WordPress API Permission Callback – Check if user is logged in - php

I have a question about permission callbacks when working with the WP Rest API. I have registered a couple of endpoints using register_rest_route and simply want to secure them so that you can’t access the content if you’re not logged in.
add_action('rest_api_init', 'register_custom_endpoints');
function register_custom_endpoints(){
register_rest_route($base, $endpoint, [
'methods' => 'POST',
'callback' => function($request){
// Return JSON data
},
'permission_callback' => function($request){
// This always returns false
return is_user_logged_in();
},
]);
}
I think my logic here is correct but is_user_logged_in() always returns false, meaning I’ll never get the data even if I’m actually logged in. All I get is a 404 response:
{code: "rest_user_invalid_id", message: "Invalid user ID.", data: {status: 404}}
I’ve been scouring the web for answers and what I’ve found is that I need to send a nonce with the request. I’ve tried sending it both as body data and as a header. If I send it in the body, I can’t verify it as it always returns false. And if I send it as a header, I get the same 404 response as stated above.
What am I missing here, and what am I doing wrong?

I recently encountered the same issue and it turns out that you need to create a nonce and pass it with your request headers.
You create the appropriate nonce with the following php code.
$nonce = wp_create_nonce( 'wp_rest' );
Then you pass that nonce with the HTTP request via the header X-WP-Nonce.
Assuming your client is on the same domain (ergo has WordPress's auth cookies set) your session should be accessible via the REST API so functions like current_user_can and is_user_logged_in work as they would outside the REST API.

A simple way to solve this is instead of permission_callback parameter that check user logged in status in the request handler function with a GET request URL parameter. You can add logged in user ID or a temporary generated nonce / hash in the URL and check on server side that they are the same or not, for example: domain.tld/wp-json/user/1/todo/
class UserAPI
{
public function __construct()
{
// do nothing
}
public function setHooks()
{
add_action('rest_api_init', [$this, 'actionRestApiInit']);
}
public function actionRestApiInit()
{
// add current user ID into GLOBALS to get it in the request handler function
$GLOBALS['user_id'] = get_current_user_id();
register_rest_route(
'user',
'(?P<userID>\d+)/(?P<action>[a-zA-Z0-9-]+)/',
[
'methods' => 'GET',
'callback' => [$this, 'runAction'],
]
);
}
public function runAction(\WP_REST_Request $data=null)
{
$params = $data->get_params();
if ((int)$params['userID']!==(int)$GLOBALS['user_id']) {
return new \WP_Error( 'rest_forbidden', __('Sorry, you are not allowed to do that.'), ['status' => 401] );
}
wp_send_json(['Logged in, run api'], 200);
}
}
(new UserAPI())->setHooks();
Or as a rest route attribute:
register_rest_route(
'user',
'(?P<userID>\d+)/(?P<action>[a-zA-Z0-9-]+)/',
[
'methods' => 'GET',
'callback' => [$this, 'userActions'],
'user_ID' => get_current_user_id(),
]
);
and than:
if ( (int) $params['userID'] !== (int) $data->get_attributes()['user_ID'] )

you can use current_user_can( 'edit_posts' );
it will only allow to admin user it work with me
for more info go to
https://developer.wordpress.org/reference/functions/current_user_can/

Related

Wordpress REST API Caching Issue - WP Engine

I have created a custom plugin that actually just registeres a few API endpoints.
Right now the issue I'm having is that all of the endpoints are working fine locally, but when I push this code to WpEngine where I've hosted my WordPress site the API responses are getting cached.
If I clear the cache through WPEngine and make the request again the API is working fine until a 200 success response is received for the first time, once success is received then from the point the endpoint is always returning the same response no matter what header, parameter value I give to that endpoint.
In the wp-config.php file I've disabled the cache - define( 'WP_CACHE', false );
also tried adding
wp_cache_flush();
nocache_headers();
in the request action call back functions too, still no success, always the response are cached.
Few Code snippets for your reference -
// This is the route I've registered
public function register_routes()
{
register_rest_route($namespace, '/config' ,[
'methods' => 'GET',
'callback' => array($this, 'Action_GetConfig'),
'permission_callback' => 'authCheck'
]);
}
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
function Action_GetConfig(WP_REST_Request $request)
{
try {
// wp_cache_flush();
// nocache_headers();
$headers = $request->get_headers();
// Basic Validation
if (IsNullOrEmptyString($headers['platform'][0]) or IsNullOrEmptyString($headers['version'][0])) {
$resp = new WP_HTTP_Response();
// $resp->set_headers( array('Cache-Control' => 'no-cache, must-revalidate, max-age=0'));
$resp->set_status(400);
return $resp;
}
// Service Invocation
$results = $this->getConfig($headers['platform'][0], $headers['version'][0]);
$resp = new WP_HTTP_Response($results);
$resp->set_status(200);
return $resp;
} catch (Throwable $e) {
//$log->error($e);
$resp = new WP_HTTP_Response($e);
$resp->set_status(500);
return $resp;
}
}
Can someone help in solving this API response caching issue? Thanks!
I believe WP ENGINE USES, WP Engine MU PLUGIN. If so with this plugin you will have access to a few functions called
wpecommon::purge_varnish_cache()
, if you pass the ID of a particular post that you were targeted for, this function will clear the post cache. But if you use the function without passing the ID then it will clear all the cache of the entire domain( I would certainly not recommend this way, as it will cause a performance issue on a large site).
function your_call_of_action(WP_REST_Request $request){
$id = $request->get_param( 'id' );
if ( FALSE === get_post_status( $id ) ) {
$resp = new WP_HTTP_Response([]);
$resp->set_status(500);
return $resp;
}
wpecommon::purge_varnish_cache( $id )
// rest of your code goes here
}
Also, you can call the following API to clear the cache :
/installs/{install_id}/purge_cache
Read the following documents:
Read this document: https://wpengine.com/support/cache/
API Document: https://wpengineapi.com/reference
I solved this issue by adding a Cache Exclusion Policy in WPEngine. Because by default in WPEngine all the Endpoints responses are cached. So if we want NOT to cache a specific route, then we have to add that route to the Exclusion list.
path: ^/wp-json/nx/services/?
I added this above Route RegEx in WPEngine.

wordpress rest api custom endpoint - add header in request API

it's my first time to create rest api with custom endpoint in wordpress the api is working fine but the problem is i want to add key and value in the header when i send request to the api , to avoid using the api in postman without adding these key and value in the header
here is the code that create the api :
add_action( 'rest_api_init', 'wl_posts');
function wl_posts(){
register_rest_route('w1/v1','fav/',[
'methods' => 'POST',
'callback' => 'test',
'header' => 'username : test'
]);
any help ?
You can create a function from you callback that gathers the param you need, and do what you need after:
<?php
function callback_name( $data ) {
$your_var = $data->get_param( 'param_name' );
// Do other stuff that you need to do
}
?>

Communicating between two WordPress site using REST API & without any authentication

I have 2 WordPress sites in 2 different domains. One is my server & another one is my client site. In my server WordPress, I have a custom WordPress REST API endpoint that accepts POST requests. The client will send some custom data to the server. Then the server processes the data & returns an output.
The issue is that I can't use any authentication method here. The API must be public & any WordPress site can send some data to the server. In the server's REST API callback function, I have added some custom sanitization & validation. Also, nothing to be saved in the DATABASE.
I have added the below code snippet to make a public API endpoint in my server.
add_action( 'rest_api_init', 'register_rest_route_abcd' );
function register_rest_route_abcd() {
register_rest_route(
'mycustomapi', 'v1/abcd',
array(
'methods' => 'POST',
'callback' => array( 'my_callback_function'),
'permission_callback' => '__return_true',
)
);
}
Then in the client file, I have used wp_safe_remote_post to post my custom data.
wp_safe_remote_post( $target_url, array('body' => $request_data) )
The requested data is custom data & there are no authentication parameters there. I got the error given below.
{"code":"rest_not_logged_in","message":"You are not currently logged in.","data":{"status":401}}
I need a public API endpoint where anyone can submit some data.
NOTE: I have a custom string like a key in the submitted data & can I use that string to authenticate API response?
Update: The issue was automatically resolved. I haven't done any changes in the code or file.
I have attached the code snippet that will be useful for someone else. Public API that accepts POST requests is always vulnerable. So be careful. I believe that later we can get some suggestions regarding it.
In server-side
add_action( 'rest_api_init', 'register_rest_route_abcd');
function register_rest_route_abcd() {
register_rest_route(
'myapi', 'v1/myroute',
array(
'methods' => 'POST,GET',
'callback' => 'my_callback_function',
'permission_callback' => '__return_true',
)
);
}
function my_callback_function($request){
//Do logic here
// Prepare response
$response = array();
$response['status'] = true;
$response['message'] = 'done';
return $response;
}
In clint side
add_action( 'template_redirect', 'ed45r_my_action', 5 );
function ed45r_my_action(){
$target_url = 'http://localhost/server-sys/wp-json/myapi/v1/myroute';
$data = array(
'key_1' => 'value_1',
'key_2' => 'value_2',
);
$request = wp_safe_remote_get( $target_url, array('body' => $data) );
if(is_wp_error($request) || wp_remote_retrieve_response_code($request) != 200){
// Know the errror
} else {
$response = wp_remote_retrieve_body( $request );
$response = json_decode($response, true);
// Process the response
}
}
In my case, the action is not a template_redirect. It is a form submission.

Finding User with Auth - Laravel

I am trying to find the logged in user in my application using Auth but i get trying to get property of non-object which i understand clearly that it is returning null.
In my code below, an event triggers my webhook and post is sent to the address below. The function orderCreateWebhook triggers but that is where the error comes from..
The line $get_template = Order::where('id', Auth::user()->id);. Why is Auth returning null please? I am logged as well because i use auth in this same controller for another function which works fine.
Is it because it a webhook ?
Controller
public function registerOrderCreateWebhook(Request $request)
{
$shop = "feas.myshopify.com";
$token = "8f43d89a64e922d7d343c1173f6d";
$shopify = Shopify::setShopUrl($shop)->setAccessToken($token);
Shopify::setShopUrl($shop)->setAccessToken($token)->post("admin/webhooks.json", ['webhook' =>
['topic' => 'orders/create',
'address' => 'https://larashop.domain.com/order-create-webhook',
'format' => 'json'
]
]);
}
public function orderCreateWebhook(Request $request)
{
$get_template = Order::where('id', Auth::user()->id);
$baseurl = "https://apps.domain.net/smsapi";
$query = "?key=7e3e4d4a6cfebc08eadc&to=number&msg=message&sender_id=Shopify";
$final_uri = $baseurl.$query;
$response = file_get_contents($final_uri);
header ("Content-Type:text/xml");
}
In your function registerOrderCreateWebhook you appear to be making a request to shopify api and providing your webhook as the address which shopify will redirect the user to upon success. If this is correct, that request does not know about the user who generated the original request that made the api request since the request is coming from a completely different origin.
You would need to pass some key along with the url and then obtain the user within orderCreateWebhook. Something like:
Shopify::setShopUrl($shop)->setAccessToken($token)->post("admin/webhooks.json",
['webhook' =>
['topic' => 'orders/create',
'address' => 'https://larashop.domain.com/order-create-webhook/some-unique-key',
'format' => 'json'
]
]);
My suggestion would be to have a unique hash stored somewhere that relates back to the user in your system, perhaps a column in your users table. I wouldn't use the user_id for security reasons. So you would end up with something like:
//route
Route::get('/order-create-webhook/{uniqueKey}', 'YourController#orderCreateWebhook');
//or
Route::post('/order-create-webhook/{uniqueKey}', 'YourController#orderCreateWebhook');
// depending on the request type used by api which calls this endpoint
// controller function
public function orderCreateWebhook($uniqueKey, Request $request)
{
$user = User::where('unique_key', $uniqueKey)->first();
$get_template = Order::where('id', Auth::user()->id);
$baseurl = "https://apps.domain.net/smsapi";
$query = "?key=7e3e4d4a6cfebc08eadc&to=number&msg=message&sender_id=Shopify";
$final_uri = $baseurl.$query;
$response = file_get_contents($final_uri);
header ("Content-Type:text/xml");
}
Is it because it a webhook ?
Yes, you can't use sessions in a webhook. It's the shopify server which is making the call. You should read the doc, it may exist a way to give an unique identifier in your call to shopify api and get it back in the webhook to find your user associated.
just use this to get authenticated user
use the facade in your class/Controller
use Illuminate\Support\Facades\Auth
public function getAuthUser(){
$user = Auth::user()
if(!is_null($user)
{
//user is authenticated
}
else
{
// no user
}
}

How to get current logged in user using Wordpress Rest Api?

I tried to add a custom request.
add_action('rest_api_init', function () {
register_rest_route( 'custom', '/login', array(
'methods' => 'GET',
'callback' => function(WP_REST_Request $request) {
return wp_get_current_user();
}
));
});
But it always returns a user with with ID = 0;
I also tried this:
add_action('rest_api_init', function () {
register_rest_route( 'custom', '/login', array(
'methods' => 'GET',
'callback' => function(WP_REST_Request $request) {
return is_user_logged_in();
}
));
});
And it always returns false.
But the user is logged in for sure.
I added my custom login
add_action('rest_api_init', function () {
register_rest_route( 'custom', '/login', array(
'methods' => 'POST',
'callback' => function(WP_REST_Request $request) {
$nonce = wp_create_nonce("wp_rest");
$user = wp_signon(array('user_login' => $_POST['username'],
'user_password' => $_POST['password'], "rememberme" => true), false);
if (is_wp_error($user)) {
return $user;
}
//do_action( 'wp_login', "capad" );
//$user['isloggedin'] = is_user_logged_in();
return array('user' => $user,
'nonce' => $nonce);
}
));
});
And I add "X-WP-Nonce" in as a header for http request
And now every request outputs: {"code":"rest_cookie_invalid_nonce","message":"Cookie nonce is invalid","data":{"status":403}}
From the Authentication chapter, in the REST API Handbook:
Cookie authentication is the basic authentication method included with
WordPress. When you log in to your dashboard, this sets up the cookies
correctly for you, so plugin and theme developers need only to have a
logged-in user.
However, the REST API includes a technique called nonces to avoid CSRF
issues. This prevents other sites from forcing you to perform actions
without explicitly intending to do so. This requires slightly special
handling for the API.
For developers using the built-in Javascript API, this is handled
automatically for you. This is the recommended way to use the API for
plugins and themes. Custom data models can extend wp.api.models.Base
to ensure this is sent correctly for any custom requests.
For developers making manual Ajax requests, the nonce will need to be
passed with each request. The API uses nonces with the action set to
wp_rest. These can then be passed to the API via the _wpnonce data
parameter (either POST data or in the query for GET requests), or via
the X-WP-Nonce header.
Here's a GET example:
https://example.tld/wp-json/wp/v2/users/me?_wpnonce=9467a0bf9c
or in your case:
https://example.tld/wp-json/custom/login/?_wpnonce=9463a0bf9c
where the nonce is created from
wp_create_nonce( 'wp_rest' );
So most likely you forgot about the nonce part when testing your custom endpoint.
Hope it helps!
I spent two days searching for a simple way without adding plugins.
first in function.php where you define your api
//enqueue the script which will use the api
function api_callings_scripts() {
wp_enqueue_script('score-script', get_template_directory_uri() . '/js/ScoreSaving.js', ['jquery'], NULL, TRUE);
// Pass nonce to JS.
wp_localize_script('score-script', 'ScoreSettings', [
'nonce' => wp_create_nonce('wp_rest'),
]);
}
add_action( 'wp_enqueue_scripts', 'api_callings_scripts' );
Then your script Ajax call cloud be something like this
jQuery.ajax({
type: "POST",
url: "/wp-json/score/update",
data: {"var1":"value1"},
beforeSend: function(xhr) {
xhr.setRequestHeader('X-WP-Nonce', ScoreSettings.nonce);
},
success:
function( data ) {
console.log( data );
}
});
Now you can use get_current_user_id() inside your API code.
1. Install and activate JWT Authentication for WP REST API plugin, also install WP REST API plugin
2. Now you can run any wordpress default api from mobile app or any other source or by postman. for example hit this url from your app or by postman. https://example.com/wp-json/wp/v2/posts
3. By app or by postman, When you will login with valid details (using rest api) you will get back a token. To login and get token, run the following url by postman or by app
https://example.com/wp-json/jwt-auth/v1/token
4. By this way you will get a token as shown in picture
Now use this token to get logged in user details, for example
5. make function in function.php
function checkloggedinuser()
{
$currentuserid_fromjwt = get_current_user_id();
print_r($currentuserid_fromjwt);
exit;
}
add_action('rest_api_init', function ()
{
register_rest_route( 'testone', 'loggedinuser',array(
'methods' => 'POST',
'callback' => 'checkloggedinuser'
));
});
6. Now again run this new url in postman or in app to get logged in user details. https://example.com/wp-json/testone/loggedinuser (replace example.com with your url)
(https://i.stack.imgur.com/tIqhS.png)
7. Also edit your .htaccess file and wp-config.php file according to instructions on pt.wordpress.org/plugins/jwt-authentication-for-wp-rest-api
Here is another way
$user_id = ""; //<- add this
add_action( 'rest_api_init', 'add_custom_users_api');
function add_custom_users_api(){
$GLOBALS['user_id'] = get_current_user_id(); //<- add this
// route url: domain.com/wp-json/mmw/v1/testing
register_rest_route( 'mmw/v1', 'testing', array(
'methods' => 'GET',
'callback' => 'get_custom_users_data',
));
}
//Customize the callback to your liking
function get_custom_users_data(){
return get_user_by( 'id', $GLOBALS['user_id'] ); //<- add this
}
If you prefer use JWT Authentication for WP REST API, it may be easier to implement with Json Web Tokens.
First you authenticate the client sending a HTTP POST request to the endpoint /wp-json/jwt-auth/v1/token sending username and password fields to generate a auth token.
A succefull response would be similar to:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9qd3QuZGV2IiwiaWF0IjoxNDM4NTcxMDUwLCJuYmYiOjE0Mzg1NzEwNTAsImV4cCI6MTQzOTE3NTg1MCwiZGF0YSI6eyJ1c2VyIjp7ImlkIjoiMSJ9fX0.YNe6AyWW4B7ZwfFE5wJ0O6qQ8QFcYizimDmBy6hCH_8",
"user_display_name": "admin",
"user_email": "admin#localhost.dev",
"user_nicename": "admin"
}
Then you pass the token each request settings the request header Authorization like:
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9qd3QuZGV2IiwiaWF0IjoxNDM4NTcxMDUwLCJuYmYiOjE0Mzg1NzEwNTAsImV4cCI6MTQzOTE3NTg1MCwiZGF0YSI6eyJ1c2VyIjp7ImlkIjoiMSJ9fX0.YNe6AyWW4B7ZwfFE5wJ0O6qQ8QFcYizimDmBy6hCH_8
I was dealing with the same issue where get_current_user_id() would return 0. After some testing I found this was only the case when sending POST requests.
With GET requests the function returned the logged in user ID, on a POST request it returned 0. This could be due to a server configuration perhaps as this was on a shared hosting environment. I did not find any posts or comments mentioning this before, but this is what happened in my case. A simple workaround could be to pass the user ID back to the frontend in a GET request, store it and send it on POST requests as an extra param.
I think it one of the most flexible way to retrieve the user id in rest callback
<?php
// register route
add_action( 'rest_api_init', function () {
register_rest_route( 'el-dashboard-api', '/generic-comments', [
'methods' => 'GET',
'callback' => 'get_generic_comments',
'login_user_id' => get_current_user_id(), // This will be pass to the rest API callback
]);
});
// rest callback
function get_generic_comments($REST_REQUEST_OBJ){
// get all the passed attrs
$attrs = $REST_REQUEST_OBJ->get_attributes();
// check is login_user_id set as attr
if( isset($attrs['login_user_id']) && intval($attrs['login_user_id']) > 0 ){
$user_id = intval($attrs['login_user_id']);
$current_user = get_user_by( 'id', $user_id );
return "Authorize";
}
return "Not authorize";
}

Categories