diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php
index 7c9c539..9144759 100644
--- a/core/includes/entity.api.php
+++ b/core/includes/entity.api.php
@@ -512,3 +512,27 @@ function hook_entity_field_info_alter(&$info, $entity_type) {
     $info['definitions']['mymodule_text']['class'] = '\Drupal\anothermodule\EntityComputedText';
   }
 }
+
+/**
+ * Alters the default access behaviour for a given field.
+ *
+ * This hook is invoked from \Drupal\Core\Entity\Field\Type\Field::access() to
+ * let modules alter access to operations on fields. As soon as one hook
+ * implementation changes $access to FALSE the access() method will return FALSE
+ * and the remaining hook implementations will not be invoked.
+ *
+ * @param bool $access
+ *   Access flag reference that can be altered. TRUE if the operation is
+ *   allowed, and FALSE if the operation is denied.
+ * @param string $operation
+ *   The operation to be performed. Possible values: 'edit', 'view'.
+ * @param \Drupal\Core\Entity\Field\Type\Field $field
+ *   The entity field object on which the operation is to be performed.
+ * @param \Drupal\user\Plugin\Core\Entity\User $account
+ *   The user account to check.
+ */
+function hook_entity_field_access_alter(&$access, $operation, $field, $account) {
+  if ($field->getName() == 'field_of_interest' && $operation == 'edit') {
+    $access = user_access('edit field of interest', $account);
+  }
+}
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php
index 49418a0..e2f7d37 100644
--- a/core/lib/Drupal/Core/Entity/Field/Type/Field.php
+++ b/core/lib/Drupal/Core/Entity/Field/Type/Field.php
@@ -289,6 +289,23 @@ public function __clone() {
    * Implements \Drupal\Core\TypedData\AccessibleInterface::access().
    */
   public function access($operation = 'view', User $account = NULL) {
-    // TODO: Implement access() method. Use item access.
+    global $user;
+    if (!isset($account)) {
+      $account = $user;
+    }
+    // Grant access per default.
+    $access = TRUE;
+    // We do not use drupal_alter() here because it only allows two context
+    // parameters which would make the signature of the hook very ugly.
+    foreach (module_implements('entity_field_access_alter') as $module) {
+      $function = $module . '_entity_field_access_alter';
+      $function($access, $operation, $this, $account);
+      // If a hook implementation altered $access to FALSE we can return
+      // immediately.
+      if ($access === FALSE) {
+        return FALSE;
+      }
+    }
+    return $access;
   }
 }
diff --git a/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityWrapper.php b/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityWrapper.php
index a2e49e3..f96f114 100644
--- a/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityWrapper.php
+++ b/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityWrapper.php
@@ -111,6 +111,7 @@ public function getTypeUri() {
   public function getProperties() {
     // Properties to skip.
     $skip = array('id');
+    $properties = array();
 
     // Create language map property structure.
     foreach ($this->entity->getTranslationLanguages() as $langcode => $language) {
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
index 3f45421..9a69ff3 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\rest\Plugin\ResourceBase;
 use Drupal\rest\ResourceResponse;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\HttpKernel\Exception\HttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -44,6 +45,14 @@ public function get($id) {
     $definition = $this->getDefinition();
     $entity = entity_load($definition['entity_type'], $id);
     if ($entity) {
+      if (!$entity->access('view')) {
+        throw new AccessDeniedHttpException();
+      }
+      foreach ($entity as $field_name => $field) {
+        if (!$field->access('view')) {
+          unset($entity->{$field_name});
+        }
+      }
       return new ResourceResponse($entity);
     }
     throw new NotFoundHttpException(t('Entity with ID @id not found', array('@id' => $id)));
@@ -63,6 +72,9 @@ public function get($id) {
    * @throws \Symfony\Component\HttpKernel\Exception\HttpException
    */
   public function post($id, EntityInterface $entity) {
+    if (!$entity->access('create')) {
+      throw new AccessDeniedHttpException();
+    }
     // Verify that the deserialized entity is of the type that we expect to
     // prevent security issues.
     $definition = $this->getDefinition();
@@ -74,6 +86,11 @@ public function post($id, EntityInterface $entity) {
     if (!$entity->isNew()) {
       throw new BadRequestHttpException();
     }
+    foreach ($entity as $field_name => $field) {
+      if (!$field->access('create')) {
+        throw new AccessDeniedHttpException(t('Access denied on creating field @field.', array('@field' => $field_name)));
+      }
+    }
     try {
       $entity->save();
       $url = url(strtr($this->plugin_id, ':', '/') . '/' . $entity->id(), array('absolute' => TRUE));
@@ -109,6 +126,21 @@ public function put($id, EntityInterface $entity) {
     if ($original_entity == FALSE) {
       throw new NotFoundHttpException();
     }
+    if (!$original_entity->access('update')) {
+      throw new AccessDeniedHttpException();
+    }
+    foreach ($entity as $field_name => $field) {
+      if (isset($entity->{$field_name})) {
+        if (!$original_entity->{$field_name}->access('update')) {
+          throw new AccessDeniedHttpException(t('Access denied on updating field @field.', array('@field' => $field_name)));
+        }
+      }
+      else {
+        if (isset($original_entity->{$field_name}) && !$original_entity->{$field_name}->access('delete')) {
+          throw new AccessDeniedHttpException(t('Access denied on deleting field @field.', array('@field' => $field_name)));
+        }
+      }
+    }
     $info = $entity->entityInfo();
     // Make sure that the entity ID is the one provided in the URL.
     $entity->{$info['entity_keys']['id']} = $id;
@@ -149,10 +181,23 @@ public function patch($id, EntityInterface $entity) {
     if ($original_entity == FALSE) {
       throw new NotFoundHttpException();
     }
+    if (!$entity->access('update')) {
+      throw new AccessDeniedHttpException();
+    }
     // Overwrite the received properties.
-    foreach ($entity->getProperties() as $name => $property) {
-      if (isset($entity->{$name})) {
-        $original_entity->{$name} = $property;
+    foreach ($entity as $field_name => $field) {
+      if (isset($entity->{$field_name})) {
+        if (empty($entity->{$field_name})) {
+          if (!$original_entity->{$field_name}->access('delete')) {
+            throw new AccessDeniedHttpException(t('Access denied on deleting field @field.', array('@field' => $field_name)));
+          }
+        }
+        else {
+          if (!$original_entity->{$field_name}->access('update')) {
+            throw new AccessDeniedHttpException(t('Access denied on updating field @field.', array('@field' => $field_name)));
+          }
+        }
+        $original_entity->{$field_name} = $field;
       }
     }
     try {
@@ -180,6 +225,9 @@ public function delete($id) {
     $definition = $this->getDefinition();
     $entity = entity_load($definition['entity_type'], $id);
     if ($entity) {
+      if (!$entity->access('delete')) {
+        throw new AccessDeniedHttpException();
+      }
       try {
         $entity->delete();
         // Delete responses have an empty body.
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php b/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php
index c5b298a..4224965 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php
@@ -41,7 +41,9 @@ public function testCreate() {
     $this->enableService('entity:' . $entity_type);
     // Create a user account that has the required permissions to create
     // resources via the web API.
-    $account = $this->drupalCreateUser(array('restful post entity:' . $entity_type));
+    $permissions = $this->entityPermissions($entity_type, 'create');
+    $permissions[] = 'restful post entity:' . $entity_type;
+    $account = $this->drupalCreateUser($permissions);
     $this->drupalLogin($account);
 
     $entity_values = $this->entityValues($entity_type);
@@ -69,6 +71,19 @@ public function testCreate() {
     }
 
     $loaded_entity->delete();
+
+    // Try to create an entity with an access protected field.
+    // @see entity_test_entity_field_access_alter()
+    $entity->field_test_text->value = 'no access value';
+    $serialized = $serializer->serialize($entity, 'drupal_jsonld');
+    $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, 'application/vnd.drupal.ld+json');
+    $this->assertResponse(403);
+    $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->value = $entity_values['field_test_text'][0]['value'];
+    $serialized = $serializer->serialize($entity, 'drupal_jsonld');
+
     // Try to create an entity without the CSRF token.
     $this->curlExec(array(
       CURLOPT_HTTPGET => FALSE,
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
index ed2d04d..abdd030 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
@@ -34,12 +34,16 @@ public static function getInfo() {
    */
   public function testDelete() {
     // Define the entity types we want to test.
-    $entity_types = array('entity_test', 'node', 'user');
+    // @todo expand this test to at least nodes and users once their access
+    // controllers are implemented.
+    $entity_types = array('entity_test');
     foreach ($entity_types as $entity_type) {
       $this->enableService('entity:' . $entity_type);
       // Create a user account that has the required permissions to delete
       // resources via the web API.
-      $account = $this->drupalCreateUser(array('restful delete entity:' . $entity_type));
+      $permissions = $this->entityPermissions($entity_type, 'delete');
+      $permissions[] = 'restful delete entity:' . $entity_type;
+      $account = $this->drupalCreateUser($permissions);
       $this->drupalLogin($account);
 
       // Create an entity programmatically.
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
index f7dbdb6..1e8f118 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
@@ -212,4 +212,29 @@ protected function drupalLogin($user) {
     }
     parent::drupalLogin($user);
   }
+
+  /**
+   * Provides the necessary user permissions for entity operations.
+   *
+   * @param string $entity_type
+   *   The entity type.
+   * @param type $operation
+   *   The operation, one of 'view', 'create', 'update' or 'delete'.
+   *
+   * @return array
+   *   The set of user permission strings.
+   */
+  protected function entityPermissions($entity_type, $operation) {
+    switch ($entity_type) {
+      case 'entity_test':
+        switch ($operation) {
+          case 'view':
+            return array('view test entity');
+          case 'create':
+          case 'update':
+          case 'delete':
+            return array('administer entity_test content');
+        }
+    }
+  }
 }
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php b/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
index 0578fc3..283aec1 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
@@ -39,9 +39,11 @@ public function testRead() {
     $entity_types = array('entity_test');
     foreach ($entity_types as $entity_type) {
       $this->enableService('entity:' . $entity_type);
-      // Create a user account that has the required permissions to delete
+      // Create a user account that has the required permissions to read
       // resources via the web API.
-      $account = $this->drupalCreateUser(array('restful get entity:' . $entity_type));
+      $permissions = $this->entityPermissions($entity_type, 'view');
+      $permissions[] = 'restful get entity:' . $entity_type;
+      $account = $this->drupalCreateUser($permissions);
       $this->drupalLogin($account);
 
       // Create an entity programmatically.
@@ -69,6 +71,17 @@ public function testRead() {
       $this->assertResponse(404);
       $this->assertEqual($response, 'Entity with ID 9999 not found', 'Response message is correct.');
 
+      // Make sure that field level access works and that the according field is
+      // not available in the response.
+      // @see entity_test_entity_field_access_alter()
+      $entity->field_test_text->value = 'no access value';
+      $entity->save();
+      $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, 'application/vnd.drupal.ld+json');
+      $this->assertResponse(200);
+      $this->assertHeader('content-type', 'application/vnd.drupal.ld+json');
+      $data = drupal_json_decode($response);
+      $this->assertFalse(isset($data['field_test_text']), 'Field access protexted field is not visible in the response.');
+
       // Try to read an entity without proper permissions.
       $this->drupalLogout();
       $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, 'application/vnd.drupal.ld+json');
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php b/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php
index fa65a2f..9d35df6 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php
@@ -39,9 +39,11 @@ public function testPatchUpdate() {
     $entity_type = 'entity_test';
 
     $this->enableService('entity:' . $entity_type);
-    // Create a user account that has the required permissions to create
+    // Create a user account that has the required permissions to update
     // resources via the web API.
-    $account = $this->drupalCreateUser(array('restful patch entity:' . $entity_type));
+    $permissions = $this->entityPermissions($entity_type, 'update');
+    $permissions[] = 'restful patch entity:' . $entity_type;
+    $account = $this->drupalCreateUser($permissions);
     $this->drupalLogin($account);
 
     // Create an entity and save it to the database.
@@ -76,6 +78,32 @@ public function testPatchUpdate() {
     $entity = entity_load($entity_type, $entity->id(), TRUE);
     $this->assertNull($entity->field_test_text->value, 'Test field has been cleared.');
 
+    // Enable access protection for the text field.
+    // @see entity_test_entity_field_access_alter()
+    $entity->field_test_text->value = 'no access value';
+    $entity->save();
+
+    // Try to empty a field that is access protected.
+    $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json');
+    $this->assertResponse(403);
+
+    // Re-load the entity from the database.
+    $entity = entity_load($entity_type, $entity->id(), TRUE);
+    $this->assertEqual($entity->field_test_text->value, 'no access value', 'Text field was not updated.');
+
+    // Try to update an access protected field.
+    $serialized = $serializer->serialize($patch_entity, 'drupal_jsonld');
+    $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json');
+    $this->assertResponse(403);
+
+    // Re-load the entity from the database.
+    $entity = entity_load($entity_type, $entity->id(), TRUE);
+    $this->assertEqual($entity->field_test_text->value, 'no access value', 'Text field was not updated.');
+
+    // Restore the valid test value.
+    $entity->field_test_text->value = $this->randomString();
+    $entity->save();
+
     // Try to update a non-existing entity with ID 9999.
     $this->httpRequest('entity/' . $entity_type . '/9999', 'PATCH', $serialized, 'application/vnd.drupal.ld+json');
     $this->assertResponse(404);
@@ -104,9 +132,11 @@ public function testPutUpdate() {
     $entity_type = 'entity_test';
 
     $this->enableService('entity:' . $entity_type);
-    // Create a user account that has the required permissions to create
+    // Create a user account that has the required permissions to update
     // resources via the web API.
-    $account = $this->drupalCreateUser(array('restful put entity:' . $entity_type));
+    $permissions = $this->entityPermissions($entity_type, 'update');
+    $permissions[] = 'restful put entity:' . $entity_type;
+    $account = $this->drupalCreateUser($permissions);
     $this->drupalLogin($account);
 
     // Create an entity and save it to the database.
@@ -146,6 +176,33 @@ public function testPutUpdate() {
     $entity = entity_load($entity_type, $entity->id(), TRUE);
     $this->assertTrue($entity->field_test_text->isEmpty(), 'Property has been deleted.');
 
+    // Enable access protection for the text field.
+    // @see entity_test_entity_field_access_alter()
+    $entity->field_test_text->value = 'no access value';
+    $entity->save();
+
+    // Try to delete an access protected field.
+    $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PUT', $serialized, 'application/vnd.drupal.ld+json');
+    $this->assertResponse(403);
+
+    // Re-load the entity from the database.
+    $entity = entity_load($entity_type, $entity->id(), TRUE);
+    $this->assertFalse($entity->field_test_text->isEmpty(), 'Text field was not deleted.');
+
+    // Try to update an access protected field.
+    $update_entity->field_test_text->value = $update_values['field_test_text'][0]['value'];
+    $serialized = $serializer->serialize($update_entity, 'drupal_jsonld');
+    $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PUT', $serialized, 'application/vnd.drupal.ld+json');
+    $this->assertResponse(403);
+
+    // Re-load the entity from the database.
+    $entity = entity_load($entity_type, $entity->id(), TRUE);
+    $this->assertEqual($entity->field_test_text->value, 'no access value', 'Text field was not updated.');
+
+    // Restore the valid test value.
+    $entity->field_test_text->value = $this->randomString();
+    $entity->save();
+
     // Try to create an entity with ID 9999.
     $this->httpRequest('entity/' . $entity_type . '/9999', 'PUT', $serialized, 'application/vnd.drupal.ld+json');
     $this->assertResponse(404);
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/FieldAccessTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/FieldAccessTest.php
new file mode 100644
index 0000000..623fb44
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/FieldAccessTest.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Entity\FieldAccessTest.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests the functionality of field access.
+ */
+class FieldAccessTest extends DrupalUnitTestBase {
+
+  /**
+   * Modules to load code from (no schema installation needed).
+   *
+   * @var array
+   */
+  public static $modules = array('field_sql_storage', 'system', 'text');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Field access tests',
+      'description' => 'Test Field level access.',
+      'group' => 'Entity API',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+    // Install field module schema and register entity_test text field.
+    $this->enableModules(array('field', 'entity_test'));
+  }
+
+  /**
+   * Tests that hook_entity_field_access_alter() is called.
+   *
+   * @see entity_test_entity_field_access_alter()
+   */
+  function testFieldAccess() {
+    $values = array(
+      'name' => $this->randomName(),
+      'user_id' => 1,
+      'field_test_text' => array(
+        'value' => 'no access value',
+        'format' => 'full_html',
+      ),
+    );
+    $entity = entity_create('entity_test', $values);
+    $this->assertFalse($entity->field_test_text->access('view'), 'Access to the field was denied.');
+  }
+}
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index 2752729..56daa96 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -251,3 +251,14 @@ function entity_test_entity_test_insert($entity) {
     throw new Exception("Test exception rollback.");
   }
 }
+
+/**
+ * Implements hook_entity_field_access_alter().
+ *
+ * @see \Drupal\system\Tests\Entity\FieldAccessTest::testFieldAccess()
+ */
+function entity_test_entity_field_access_alter(&$access, $operation, $field, $account) {
+  if ($field->getName() == 'field_test_text' && $field->value == 'no access value') {
+    $access = FALSE;
+  }
+}
