Why does node_access_view_all_nodes() return true for anonymous users? On a Vanilla Drupal install (tested on D8 and D9), this will print "1":

drush eval 'echo node_access_view_all_nodes() . PHP_EOL;'


Stepping through the code reveals that it happens because there is a default grant for ALL nodes (nid = 0) for anonymous users in the node_access table (as far as I can see).

My question is about the rationale of behind this. The function description states:


  Determines whether the user has a global viewing grant for all nodes.


Why would the anonymous user have the permission to view all nodes by default? Why does this not correspond to what I see when trying to access various nodes as the anonymous user? Some nodes (e.g. unpublished nodes or anything blocked by a node_access hook) do return 403 when trying to load them as the anonymous user, so it obviously does not have access to all nodes. Is there another level of access checking hidden somewhere?
whenever drupal needs to check if a current user has the right or not to see a specific node it passes through this process :
1. check if current user bypass node access
In determining access rights for a node, \Drupal\node\NodeAccessControlHandler first checks whether the user has the &quot;bypass node access&quot; permission. Such users have unrestricted access to all nodes. user 1 (administrator) will always pass this check.

2.check Hook_node_access implementations
Next, all implementations of hook_node_access() will be called. Each implementation may explicitly allow, explicitly forbid, or ignore the access request. If at least one module says to forbid the request, it will be rejected. If no modules deny the request and at least one says to allow it, the request will be permitted,
in the Example bellow whenever any user(except admin) tries to view a node we will check if field_prevent_webmaster is checked the user will get access forbidden message.
/**
 * Implements hook_ENTITY_TYPE_access
 */
function modul_name_node_access(\Drupal\node\NodeInterface $node, $operation, AccountInterface $account) {

  // 1.limit the action according to the  operation performed on the requested entity
  switch($operation){
    case 'view': // when a user visit a node page ex : /node/52

      // 2.limit the current operation according to the current account information
      if( in_array('webmaster',$account-&gt;getRoles()) ){
        //3. then further limit the current operation according to the current requested node entity field
        // ex : if field_prevent_webmaster is checked prevent a user form
        // viewing the node through the /node/{id}
        if($node-&gt;hasField('field_prevent_webmaster') ) {
          if ($node-&gt;field_prevent_webmaster-&gt;value == TRUE) {
            return AccessResult::forbidden(); // send Access denied message to the client
          }
        }

        if($node-&gt;hasField('field_allow_webmaster') ) {
          if ($node-&gt;field_allow_webmaster-&gt;value == TRUE) {
            return AccessResult::allowed(); // send Access denied message to the client
          }
        }

      }
      break;

    case 'delete':// in /admin/content page u won't find the 'delete' button
      if( in_array('webmaster',$account-&gt;getRoles()) ){

        if($node-&gt;hasField('field_prevent_webmaster') ){
          if(  $node-&gt;field_prevent_webmaster-&gt;value == true){
            return AccessResult::forbidden();
          }
        }

        if($node-&gt;hasField('field_allow_webmaster') ) {
          if ($node-&gt;field_allow_webmaster-&gt;value == TRUE) {
            return AccessResult::allowed(); // send Access denied message to the client
          }
        }

      }
      break;

    case 'update': // in /admin/content page u won't find the 'update' button
      if( in_array('webmaster',$account-&gt;getRoles()) ){

        if( $node-&gt;hasField('field_prevent_webmaster')){
          if(  $node-&gt;field_prevent_webmaster-&gt;value == true){
            return AccessResult::forbidden();
          }
        }

        if($node-&gt;hasField('field_allow_webmaster') ) {
          if ($node-&gt;field_allow_webmaster-&gt;value == TRUE) {
            return AccessResult::allowed();
          }
        }

      }
      break;
  }
}

3.Getting permissions from node_access table
If all modules ignore the access request, then the node_access table is used to determine access and this more complicated then the previous 2 so we need to understand :
the node access table

nid: The node id that will be affected by this permission line.
gid(like user): the grant id, is simply a way to associate a user with a node.
realm(like user type): It can be pretty much anything you want to consider it as a user type that groups a set of grant ids (gid).
grant_view: Boolean indicating whether a user with the realm/grant pair can view this node.
grant_update: Boolean indicating whether a user with the realm/grant pair can edit this node.
grant_delete: Boolean indicating whether a user with the realm/grant pair can delete this node.

Example according to the node_access table bellow:

the user that have gid=2 and realm=node_atuthor, he can view and update the node 52.
the user that have gid=2 but a realm=node_super_user, he can view , update and delete the node 52.


Hook_node_access_records()
when a node is saved this hook will be executed, which enables us to fill the node_access table with the permission privileges that have already explore in the previous step.
/**
 * Implements HOOK_node_access_records
 **/
function entity_access_node_access_records(\Drupal\node\NodeInterface $node) {
   $grants = [];
   if( $node-&gt;isPublished() == TRUE) {
     $grants = [
       [
         'realm' =&gt; 'node_author',
         'gid' =&gt; $node-&gt;getOwnerId(),
         'grant_view' =&gt; 1,
         'grant_update' =&gt; 1,
         'grant_delete' =&gt; 0,
         'priority' =&gt; 0,
       ],
       [
         'realm' =&gt; 'node_viewer',
         'gid' =&gt; $node-&gt;getOwnerId(),
         'grant_view' =&gt; 1,
         'grant_update' =&gt; 0,
         'grant_delete' =&gt; 0,
         'priority' =&gt; 0,
       ],
       [
         'realm' =&gt; 'node_super_user',
         'gid' =&gt; $node-&gt;getOwnerId(),
         'grant_view' =&gt; 1,
         'grant_update' =&gt; 1,
         'grant_delete' =&gt; 1,
         'priority' =&gt; 0,
       ]
     ];
   }
  return $grants;
}

Hook_node_grants()
is called every time access to a node is requested to determine access and this is what is used to check the node_access table and decide who what nodes the current user can see:
/**
 * Implements Implements HOOK_node_grants
 */
function entity_access_node_grants(AccountInterface $account, $op) {

  // Allow the user that have 'see_omar_is_nodes' permission and that is also not omar :
  if ($account-&gt;hasPermission('see_omar_is_nodes') &amp;&amp; $account-&gt;id() != 3){
      // to get This gid realm grants ( to see his content + Omar is content)
      $grants = [
        'node_super_user' =&gt; [$account-&gt;id()],
        'node_viewer' =&gt; [3]
      ] ;
  }


 // else if the user acount id =3 (omar)
  else if ($account-&gt;id() == 3){
    // get This gid realm grants of current user
    $grants =[
      'node_author' =&gt; [3]
    ] ;
  }