How to Disable/Close REST API from Public Access?

However, WordPress developers strongly DO NOT recommend completely disabling the REST API, as it will break the functionality of WordPress in the admin area starting from version 5, where REST is used for the Block editor of post content.

To keep the REST API functional while closing all routes from public access, you can use hooks. There are several options:

Option 1:
# Closes all REST API routes from public access
add_filter( 'rest_authentication_errors', function( $result ){

	if( is_null( $result ) && ! current_user_can('edit_others_posts') ){
		return new WP_Error( 'rest_forbidden', 'You are not currently logged in.', [ 'status'=>401 ] );
	}

	return $result;
} );

You can insert this code into the theme's functions.php file or anywhere else.

This code allows access to the REST API only for authorized users with editor rights and above.

The check ! current_user_can('edit_others_posts') can be replaced with something more suitable for your site. For example, you can simplify it and make the REST API accessible to all authorized users (regardless of their rights). To do this, replace the check with ! is_user_logged_in().

IMPORTANT! Closing routes in this way is dangerous because there may be routes for users on the site, such as form submissions on the front end. This method will close absolutely all routes including those that may be created by plugins or themes for unauthorized users. Therefore, I recommend using the option below through the hook rest_pre_dispatch, which allows closing specified routes without affecting others.

Option 2:

Very similar to the first, but here we do not have access to the request and REST server class.

Immediately after the hook rest_authentication_errors (if the check passes), the method WP_REST_Server::dispatch() is triggered, and at its beginning the hook rest_pre_dispatch is executed. Technically, this is exactly the same hook, where you can return WP_Error and any REST request will see this error. That is, the first option can be rewritten with the following code, where we will have access to the server object ($rest_server) and the request object ($request). This, in turn, allows closing a group of routes by namespace or specific routes:

add_filter( 'rest_pre_dispatch', 'close_rest_api_routes', 10, 3 );

/**
 * Close REST API routes from public access.
 *
 * @param WP_REST_Response|WP_Error|null $result
 * @param WP_REST_Server                 $rest_server
 * @param WP_REST_Request                $request
 *
 * @return WP_Error
 */
function close_rest_api_routes( $result, $rest_server, $request ){

	// maybe authentication error already set
	if( ! is_null( $result ) ){
		return $result;
	}

	if(
		// only for `/wp/v2` namespace
		'/wp/v2' === substr( $request->get_route(), 0, 6 )
		// Administrator
		&& ! current_user_can( 'manage_options' )
	){
		return new WP_Error( 'rest_not_logged_in', 'Your capability is low.', [ 'status' => 401 ] );
	}

	return $result;
}
Option 3:

Use the plugin Disable WP REST API