diff --git a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
index 6a24454..8ae74de 100644
--- a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
+++ b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
@@ -63,6 +63,11 @@ public function __construct(LinkManagerInterface $link_manager, EntityManagerInt
    * Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize()
    */
   public function normalize($entity, $format = NULL, array $context = array()) {
+    $context += array(
+      'account' => NULL,
+      'included_fields' => NULL,
+    );
+
     // Create the array of normalized fields, starting with the URI.
     /** @var $entity \Drupal\Core\Entity\ContentEntityInterface */
     $normalized = array(
@@ -90,9 +95,12 @@ public function normalize($entity, $format = NULL, array $context = array()) {
     // Ignore the entity ID and revision ID.
     $exclude = array($entity->getEntityType()->getKey('id'), $entity->getEntityType()->getKey('revision'));
     foreach ($fields as $field) {
-      if (in_array($field->getFieldDefinition()->getName(), $exclude)) {
+      // Continue if this is an excluded field or the current user does not have
+      // access to view it.
+      if (in_array($field->getFieldDefinition()->getName(), $exclude) || !$field->access('view', $context['account'])) {
         continue;
       }
+
       $normalized_property = $this->serializer->normalize($field, $format, $context);
       $normalized = NestedArray::mergeDeep($normalized, $normalized_property);
     }
diff --git a/core/modules/hal/src/Tests/EntityTest.php b/core/modules/hal/src/Tests/EntityTest.php
index 91d917c..ef20228 100644
--- a/core/modules/hal/src/Tests/EntityTest.php
+++ b/core/modules/hal/src/Tests/EntityTest.php
@@ -92,6 +92,9 @@ public function testTerm() {
     $vocabulary = entity_create('taxonomy_vocabulary', array('vid' => 'example_vocabulary'));
     $vocabulary->save();
 
+    $account = entity_create('user', array('name' => $this->randomMachineName()));
+    $account->save();
+
     // @todo Until https://www.drupal.org/node/2327935 is fixed, if no parent is
     // set, the test fails because target_id => 0 is reserialized to NULL.
     $term_parent = entity_create('taxonomy_term', array(
@@ -113,9 +116,9 @@ public function testTerm() {
     $original_values = $term->toArray();
     unset($original_values['tid']);
 
-    $normalized = $this->serializer->normalize($term, $this->format);
+    $normalized = $this->serializer->normalize($term, $this->format, ['account' => $account]);
 
-    $denormalized_term = $this->serializer->denormalize($normalized, 'Drupal\taxonomy\Entity\Term', $this->format);
+    $denormalized_term = $this->serializer->denormalize($normalized, 'Drupal\taxonomy\Entity\Term', $this->format, ['account' => $account]);
 
     // Verify that the ID and revision ID were skipped by the normalizer.
     $this->assertEqual(NULL, $denormalized_term->id());
@@ -133,8 +136,8 @@ public function testComment() {
     $node_type = entity_create('node_type', array('type' => 'example_type'));
     $node_type->save();
 
-    $user = entity_create('user', array('name' => $this->randomMachineName()));
-    $user->save();
+    $account = entity_create('user', array('name' => $this->randomMachineName()));
+    $account->save();
 
     // Add comment type.
     $this->container->get('entity.manager')->getStorage('comment_type')->create(array(
@@ -147,7 +150,7 @@ public function testComment() {
 
     $node = entity_create('node', array(
       'title' => $this->randomMachineName(),
-      'uid' => $user->id(),
+      'uid' => $account->id(),
       'type' => $node_type->id(),
       'status' => NODE_PUBLISHED,
       'promote' => 1,
@@ -160,7 +163,7 @@ public function testComment() {
     $node->save();
 
     $parent_comment = entity_create('comment', array(
-      'uid' => $user->id(),
+      'uid' => $account->id(),
       'subject' => $this->randomMachineName(),
       'comment_body' => [
         'value' => $this->randomMachineName(),
@@ -173,7 +176,7 @@ public function testComment() {
     $parent_comment->save();
 
     $comment = entity_create('comment', array(
-      'uid' => $user->id(),
+      'uid' => $account->id(),
       'subject' => $this->randomMachineName(),
       'comment_body' => [
         'value' => $this->randomMachineName(),
@@ -189,10 +192,16 @@ public function testComment() {
     $comment->save();
 
     $original_values = $comment->toArray();
-    unset($original_values['cid']);
+    // cid will not exist and hostname will always be denied view access.
+    unset($original_values['cid'], $original_values['hostname']);
 
-    $normalized = $this->serializer->normalize($comment, $this->format);
-    $denormalized_comment = $this->serializer->denormalize($normalized, 'Drupal\comment\Entity\Comment', $this->format);
+    $normalized = $this->serializer->normalize($comment, $this->format, ['account' => $account]);
+
+    // Assert that the hostname field does not appear at all in the normalized
+    // data.
+    $this->assertFalse(array_key_exists('hostname', $normalized), 'Hostname was not found in normalized comment data.');
+
+    $denormalized_comment = $this->serializer->denormalize($normalized, 'Drupal\comment\Entity\Comment', $this->format, ['account' => $account]);
 
     // Verify that the ID and revision ID were skipped by the normalizer.
     $this->assertEqual(NULL, $denormalized_comment->id());
diff --git a/core/modules/rest/src/Tests/CreateTest.php b/core/modules/rest/src/Tests/CreateTest.php
index affdfb5..e547c2c 100644
--- a/core/modules/rest/src/Tests/CreateTest.php
+++ b/core/modules/rest/src/Tests/CreateTest.php
@@ -41,7 +41,7 @@ public function testCreate() {
 
       $entity_values = $this->entityValues($entity_type);
       $entity = entity_create($entity_type, $entity_values);
-      $serialized = $serializer->serialize($entity, $this->defaultFormat);
+      $serialized = $serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
       // Create the entity over the REST API.
       $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
       $this->assertResponse(201);
@@ -68,23 +68,24 @@ public function testCreate() {
       // Try to create an entity with an access protected field.
       // @see entity_test_entity_field_access()
       if ($entity_type == 'entity_test') {
-        $entity->field_test_text->value = 'no access value';
-        $serialized = $serializer->serialize($entity, $this->defaultFormat);
-        $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
+        $context = ['account' => $account];
+        $normalized = $serializer->normalize($entity, $this->defaultFormat, $context);
+        $normalized['field_test_text'][0]['value'] = 'no access value';
+        $this->httpRequest('entity/' . $entity_type, 'POST', $serializer->serialize($normalized, $this->defaultFormat, $context), $this->defaultMimeType);
         $this->assertResponse(403);
         $this->assertFalse(entity_load_multiple($entity_type, NULL, TRUE), 'No entity has been created in the database.');
 
         // Try to create a field with a text format this user has no access to.
         $entity->field_test_text->value = $entity_values['field_test_text'][0]['value'];
         $entity->field_test_text->format = 'full_html';
-        $serialized = $serializer->serialize($entity, $this->defaultFormat);
+        $serialized = $serializer->serialize($entity, $this->defaultFormat, $context);
         $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
         $this->assertResponse(422);
         $this->assertFalse(entity_load_multiple($entity_type, NULL, TRUE), 'No entity has been created in the database.');
 
         // Restore the valid test value.
         $entity->field_test_text->format = 'plain_text';
-        $serialized = $serializer->serialize($entity, $this->defaultFormat);
+        $serialized = $serializer->serialize($entity, $this->defaultFormat, $context);
       }
 
       // Try to send invalid data that cannot be correctly deserialized.
@@ -98,7 +99,7 @@ public function testCreate() {
       // Try to send invalid data to trigger the entity validation constraints.
       // Send a UUID that is too long.
       $entity->set('uuid', $this->randomMachineName(129));
-      $invalid_serialized = $serializer->serialize($entity, $this->defaultFormat);
+      $invalid_serialized = $serializer->serialize($entity, $this->defaultFormat, $context);
       $response = $this->httpRequest('entity/' . $entity_type, 'POST', $invalid_serialized, $this->defaultMimeType);
       $this->assertResponse(422);
       $error = Json::decode($response);
diff --git a/core/modules/rest/src/Tests/UpdateTest.php b/core/modules/rest/src/Tests/UpdateTest.php
index 57bb0ab..6f58844 100644
--- a/core/modules/rest/src/Tests/UpdateTest.php
+++ b/core/modules/rest/src/Tests/UpdateTest.php
@@ -40,6 +40,8 @@ public function testPatchUpdate() {
     $account = $this->drupalCreateUser($permissions);
     $this->drupalLogin($account);
 
+    $context = ['account' => $account];
+
     // Create an entity and save it to the database.
     $entity = $this->entityCreate($entity_type);
     $entity->save();
@@ -52,7 +54,7 @@ public function testPatchUpdate() {
     $patch_entity = entity_create($entity_type, $patch_values);
     // We don't want to overwrite the UUID.
     unset($patch_entity->uuid);
-    $serialized = $serializer->serialize($patch_entity, $this->defaultFormat);
+    $serialized = $serializer->serialize($patch_entity, $this->defaultFormat, $context);
 
     // Update the entity over the REST API.
     $this->httpRequest($entity->getSystemPath(), 'PATCH', $serialized, $this->defaultMimeType);
@@ -64,7 +66,7 @@ public function testPatchUpdate() {
 
     // Make sure that the field does not get deleted if it is not present in the
     // PATCH request.
-    $normalized = $serializer->normalize($patch_entity, $this->defaultFormat);
+    $normalized = $serializer->normalize($patch_entity, $this->defaultFormat, $context);
     unset($normalized['field_test_text']);
     $serialized = $serializer->encode($normalized, $this->defaultFormat);
     $this->httpRequest($entity->getSystemPath(), 'PATCH', $serialized, $this->defaultMimeType);
@@ -100,8 +102,9 @@ public function testPatchUpdate() {
     $this->assertEqual($entity->field_test_text->value, 'no delete access value', 'Text field was not deleted.');
 
     // Try to update an access protected field.
-    $patch_entity->get('field_test_text')->value = 'no access value';
-    $serialized = $serializer->serialize($patch_entity, $this->defaultFormat);
+    $normalized = $serializer->normalize($patch_entity, $this->defaultFormat, $context);
+    $normalized['field_test_text'][0]['value'] = 'no access value';
+    $serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
     $this->httpRequest($entity->getSystemPath(), 'PATCH', $serialized, $this->defaultMimeType);
     $this->assertResponse(403);
 
@@ -114,7 +117,7 @@ public function testPatchUpdate() {
       'value' => 'test',
       'format' => 'full_html',
     ));
-    $serialized = $serializer->serialize($patch_entity, $this->defaultFormat);
+    $serialized = $serializer->serialize($patch_entity, $this->defaultFormat, $context);
     $this->httpRequest($entity->getSystemPath(), 'PATCH', $serialized, $this->defaultMimeType);
     $this->assertResponse(422);
 
@@ -139,7 +142,7 @@ public function testPatchUpdate() {
     // Try to send invalid data to trigger the entity validation constraints.
     // Send a UUID that is too long.
     $entity->set('uuid', $this->randomMachineName(129));
-    $invalid_serialized = $serializer->serialize($entity, $this->defaultFormat);
+    $invalid_serialized = $serializer->serialize($entity, $this->defaultFormat, $context);
     $response = $this->httpRequest($entity->getSystemPath(), 'PATCH', $invalid_serialized, $this->defaultMimeType);
     $this->assertResponse(422);
     $error = Json::decode($response);
diff --git a/core/modules/serialization/serialization.services.yml b/core/modules/serialization/serialization.services.yml
index 389ac05..f32fac0 100644
--- a/core/modules/serialization/serialization.services.yml
+++ b/core/modules/serialization/serialization.services.yml
@@ -7,6 +7,11 @@ services:
     tags:
       - { name: normalizer }
     arguments: ['@entity.manager']
+  serializer.normalizer.content_entity:
+      class: Drupal\serialization\Normalizer\ContentEntityNormalizer
+      tags:
+        - { name: normalizer }
+      arguments: ['@entity.manager']
   serializer.normalizer.entity:
     class: Drupal\serialization\Normalizer\EntityNormalizer
     tags:
diff --git a/core/modules/serialization/src/Normalizer/ContentEntityNormalizer.php b/core/modules/serialization/src/Normalizer/ContentEntityNormalizer.php
new file mode 100644
index 0000000..8ed3c91
--- /dev/null
+++ b/core/modules/serialization/src/Normalizer/ContentEntityNormalizer.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\serialization\Normalizer\ContentEntityNormalizer.
+ */
+
+namespace Drupal\serialization\Normalizer;
+
+/**
+ * Normalizes/denormalizes Drupal content entities into an array structure.
+ */
+class ContentEntityNormalizer extends EntityNormalizer {
+
+  /**
+   * The interface or class that this Normalizer supports.
+   *
+   * @var array
+   */
+  protected $supportedInterfaceOrClass = ['Drupal\Core\Entity\ContentEntityInterface'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function normalize($object, $format = NULL, array $context = array()) {
+    $context += array(
+      'account' => NULL,
+    );
+
+    $attributes = [];
+    foreach ($object as $name => $field) {
+      if ($field->access('view', $context['account'])) {
+        $attributes[$name] = $this->serializer->normalize($field, $format, $context);
+      }
+    }
+
+    return $attributes;
+  }
+
+}
diff --git a/core/modules/serialization/src/Tests/EntitySerializationTest.php b/core/modules/serialization/src/Tests/EntitySerializationTest.php
index 849b271..d45c2a6 100644
--- a/core/modules/serialization/src/Tests/EntitySerializationTest.php
+++ b/core/modules/serialization/src/Tests/EntitySerializationTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Component\Utility\String;
+use Drupal\user\Entity\User;
 
 /**
  * Tests that entities can be serialized to supported core formats.
@@ -48,6 +49,8 @@ class EntitySerializationTest extends NormalizerTestBase {
   protected function setUp() {
     parent::setUp();
 
+    // User create needs sequence table.
+    $this->installSchema('system', array('sequences'));
     // Create a test entity to serialize.
     $this->values = array(
       'name' => $this->randomMachineName(),
@@ -105,6 +108,17 @@ public function testNormalize() {
       $this->assertEqual($expected[$fieldName], $normalized[$fieldName], "ComplexDataNormalizer produces expected array for $fieldName.");
     }
     $this->assertEqual(array_diff_key($normalized, $expected), array(), 'No unexpected data is added to the normalized array.');
+
+    // Test password isn't available.
+    $account = User::create([
+      'name' => 'foo',
+      'mail' => 'foo@example.com',
+      'pass' => '123456',
+    ]);
+    $account->save();
+    $normalized = $this->serializer->normalize($account);
+    $this->assertTrue(empty($normalized['pass']));
+    $this->assertTrue(empty($normalized['mail']));
   }
 
   /**
diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/ContentEntityNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/ContentEntityNormalizerTest.php
new file mode 100644
index 0000000..d9ca3e8
--- /dev/null
+++ b/core/modules/serialization/tests/src/Unit/Normalizer/ContentEntityNormalizerTest.php
@@ -0,0 +1,155 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\serialization\Unit\Normalizer\ContentEntityNormalizerTest.
+ */
+
+namespace Drupal\Tests\serialization\Unit\Normalizer;
+
+use Drupal\serialization\Normalizer\ContentEntityNormalizer;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\serialization\Normalizer\ContentEntityNormalizer
+ * @group serialization
+ */
+class ContentEntityNormalizerTest extends UnitTestCase {
+
+  /**
+   * The mock entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityManager;
+
+  /**
+   * The mock serializer.
+   *
+   * @var \Symfony\Component\Serializer\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $serializer;
+
+  /**
+   * The normalizer under test.
+   *
+   * @var \Drupal\serialization\Normalizer\ContentEntityNormalizer
+   */
+  protected $contentEntityNormalizer;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+    $this->contentEntityNormalizer = new ContentEntityNormalizer($this->entityManager);
+    $this->serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer')
+      ->disableOriginalConstructor()
+      ->setMethods(array('normalize'))
+      ->getMock();
+    $this->contentEntityNormalizer->setSerializer($this->serializer);
+  }
+
+  /**
+   * @covers ::supportsNormalization
+   */
+  public function testSupportsNormalization() {
+    $content_mock = $this->getMock('Drupal\Core\Entity\ContentEntityInterface');
+    $config_mock = $this->getMock('Drupal\Core\Entity\ConfigEntityInterface');
+    $this->assertTrue($this->contentEntityNormalizer->supportsNormalization($content_mock));
+    $this->assertFalse($this->contentEntityNormalizer->supportsNormalization($config_mock));
+  }
+
+  /**
+   * Tests the normalize() method.
+   *
+   * @covers ::normalize
+   */
+  public function testNormalize() {
+    $this->serializer->expects($this->any())
+      ->method('normalize')
+      ->with($this->containsOnlyInstancesOf('Drupal\Core\Field\FieldItemListInterface'), 'test_format', ['account' => NULL])
+      ->will($this->returnValue('test'));
+
+    $definitions = array(
+      'field_1' => $this->createMockFieldListItem(),
+      'field_2' => $this->createMockFieldListItem(FALSE),
+    );
+    $content_entity_mock = $this->createMockForContentEntity($definitions);
+
+    $normalized = $this->contentEntityNormalizer->normalize($content_entity_mock, 'test_format');
+
+    $this->assertArrayHasKey('field_1', $normalized);
+    $this->assertEquals('test', $normalized['field_1']);
+    $this->assertArrayNotHasKey('field_2', $normalized);
+  }
+
+  /**
+   * Tests the normalize() method with account context passed.
+   *
+   * @covers ::normalize
+   */
+  public function testNormalizeWithAccountContext() {
+    $mock_account = $this->getMock('Drupal\Core\Session\AccountInterface');
+
+    $context = [
+      'account' => $mock_account,
+    ];
+
+    $this->serializer->expects($this->any())
+      ->method('normalize')
+      ->with($this->containsOnlyInstancesOf('Drupal\Core\Field\FieldItemListInterface'), 'test_format', $context)
+      ->will($this->returnValue('test'));
+
+    // The mock account should get passed directly into the access() method on
+    // field items from $context['account'].
+    $definitions = array(
+      'field_1' => $this->createMockFieldListItem(TRUE, $mock_account),
+      'field_2' => $this->createMockFieldListItem(FALSE, $mock_account),
+    );
+    $content_entity_mock = $this->createMockForContentEntity($definitions);
+
+    $normalized = $this->contentEntityNormalizer->normalize($content_entity_mock, 'test_format', $context);
+
+    $this->assertArrayHasKey('field_1', $normalized);
+    $this->assertEquals('test', $normalized['field_1']);
+    $this->assertArrayNotHasKey('field_2', $normalized);
+  }
+
+  /**
+   * Creates a mock content entity.
+   *
+   * @param $definitions
+   *
+   * @return \PHPUnit_Framework_MockObject_MockObject
+   */
+  public function createMockForContentEntity($definitions) {
+    $content_entity_mock = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
+      ->disableOriginalConstructor()
+      ->setMethods(array('getFields'))
+      ->getMockForAbstractClass();
+    $content_entity_mock->expects($this->once())
+      ->method('getFields')
+      ->will($this->returnValue($definitions));
+
+    return $content_entity_mock;
+  }
+
+  /**
+   * Creates a mock field list item.
+   *
+   * @param bool $access
+   *
+   * @return \Drupal\Core\Field\FieldItemListInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected function createMockFieldListItem($access = TRUE, $user_context = NULL) {
+    $mock = $this->getMock('Drupal\Core\Field\FieldItemListInterface');
+    $mock->expects($this->once())
+      ->method('access')
+      ->with('view', $user_context)
+      ->will($this->returnValue($access));
+
+    return $mock;
+  }
+
+}
