Drupal now uses PHP's inbuilt ability to generate session IDs. This means that session_id() and \Drupal::service('session')->getId() cannot be trusted to return an ID even if a Drupal session is started. This is because Drupal uses lazy sessions and only will properly start a session when it has determined a write is necessary.
Code should not use the session ID. If modules need a unique identifier for anonymous users that persists you should create your identifier and store it in session. This is how core's shared tempstore works for anonymous users. When necessary it stores a random string in the core.tempstore.shared.owner session property. The random value is generated using Crypt::randomBytesBase64().
This means that Drupal now supports the following PHP session configuration:
Session IDs for anonymous users
Drupal tries to avoid creating a session as much as possible for anonymous users. Some modules, like flag module, though effectively have user interaction, which requires them to open up a session.
Those modules used the session ID to uniquely identify the user, which is now deprecated. Instead, the module should store a unique identifier in the session itself. Attached below is a code statement used by TempStore:
if (!$session->has('core.tempstore.private.owner')) {
// This generates a unique identifier for the user
$session->set('core.tempstore.private.owner', Crypt::randomBytesBase64());
}
Implications for hook_user_login()
This change results in the session ID being determined after hook_user_login() implementations have been triggered. If implementations incorrectly send a response, for example interrupting execution to redirect during hook execution, then incorrect headers will be sent and login will fail. Implementations must either save the session themselves as per https://www.drupal.org/node/2023537 or better yet be re-implemented to not send a response from hook_user_login().