diff --git a/poll.install b/poll.install
index 20fa756..8481777 100644
--- a/poll.install
+++ b/poll.install
@@ -14,6 +14,12 @@ function poll_schema() {
   $schema['poll_vote'] = array(
     'description' => 'Stores per-{users} votes for each {poll}.',
     'fields' => array(
+      'id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => "The {users}'s vote id.",
+      ),
       'chid' => array(
         'type' => 'int',
         'unsigned' => TRUE,
@@ -47,7 +53,7 @@ function poll_schema() {
         'description' => 'The timestamp of the vote creation.',
       ),
     ),
-    'primary key' => array('pid', 'uid', 'hostname'),
+    'primary key' => array('id'),
     'foreign keys' => array(
       'poll_entity' => array(
         'table' => 'poll',
@@ -59,9 +65,7 @@ function poll_schema() {
       ),
     ),
     'indexes' => array(
-      'chid' => array('chid'),
-      'hostname' => array('hostname'),
-      'uid' => array('uid'),
+      'id' => array('id'),
     ),
   );
 
@@ -125,3 +129,23 @@ function poll_update_8001() {
   $field_schema['poll__choice']['indexes']['choice_target_id'] = ['choice_target_id'];
   \Drupal::keyValue('entity.storage_schema.sql')->set('poll.field_schema_data.choice', $field_schema);
 }
+
+/**
+ * Add id - unique key for votes.
+ */
+function poll_update_8002() {
+  $schema = \Drupal::database()->schema();
+  $schema->dropIndex('poll_vote', 'chid');
+  $schema->dropIndex('poll_vote', 'hostname');
+  $schema->dropIndex('poll_vote', 'uid');
+  $schema->dropPrimaryKey('poll_vote');
+  $target_id_schema = [
+    'type' => 'serial',
+    'unsigned' => TRUE,
+    'not null' => TRUE,
+    'description' => "The {users}'s vote id.",
+  ];
+
+  $schema->addField('poll_vote', 'id', $target_id_schema, ['primary key' => ['id']]);
+  $schema->addIndex('poll_vote', 'id', ['id'], ['fields' => ['id' => $target_id_schema]]);
+}
diff --git a/src/Entity/Poll.php b/src/Entity/Poll.php
index 1584a36..300f7b0 100644
--- a/src/Entity/Poll.php
+++ b/src/Entity/Poll.php
@@ -195,6 +195,14 @@ class Poll extends ContentEntityBase implements PollInterface {
   /**
    * {@inheritdoc}
    */
+  public function getVoteRestrictions() {
+    $restrictions = $this->get('anonymous_vote_restrictions')->value;
+    return empty($restrictions) ? 'ip' : $restrictions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['id'] = BaseFieldDefinition::create('integer')
       ->setLabel(t('Poll ID'))
@@ -305,6 +313,24 @@ class Poll extends ContentEntityBase implements PollInterface {
         'weight' => 1,
       ));
 
+    $fields['anonymous_vote_restrictions'] = BaseFieldDefinition::create('list_string')
+      ->setLabel(t('Anonymous vote restrictions'))
+      ->setDescription(t('Enable to allow votes from same IP.'))
+      ->setSetting('allowed_values', [
+        'ip' => t('One vote per IP'),
+        'session' => t('One vote per session'),
+        'cookie' => t('One vote per browser (cookie)'),
+      ])
+      ->setDefaultValue('ip')
+      ->setRequired(TRUE)
+      ->setDisplayOptions('form', array(
+        'type' => 'options_select',
+        'settings' => array(
+          'display_label' => TRUE,
+        ),
+        'weight' => 1,
+      ));
+
     $fields['cancel_vote_allow'] = BaseFieldDefinition::create('boolean')
       ->setLabel(t('Allow cancel votes'))
       ->setDescription(t('A flag indicating whether users may cancel their vote.'))
diff --git a/src/Form/PollForm.php b/src/Form/PollForm.php
index 0c92f87..01ad0c5 100644
--- a/src/Form/PollForm.php
+++ b/src/Form/PollForm.php
@@ -25,6 +25,12 @@ class PollForm extends ContentEntityForm {
       }
     }
 
+    $form['anonymous_vote_restrictions']['#states'] = [
+      'visible' => [
+        'input[name="anonymous_vote_allow[value]"]' => ['checked' => TRUE],
+      ],
+    ];
+
     $form['#attached'] = ['library' => ['poll/admin']];
 
     return $form;
diff --git a/src/Form/PollViewForm.php b/src/Form/PollViewForm.php
index f576194..7dd239c 100644
--- a/src/Form/PollViewForm.php
+++ b/src/Form/PollViewForm.php
@@ -61,7 +61,10 @@ class PollViewForm extends FormBase {
           // If this happened, then the form submission was likely a cached page.
           // Force a session for this user so he can see the results.
           drupal_set_message($this->t('Your vote for this poll has already been submitted.'), 'error');
-          $_SESSION['poll_vote'][$this->poll->id()] = FALSE;
+
+          if ($this->poll->getVoteRestrictions() !== 'session') {
+            $_SESSION['poll_vote'][$this->poll->id()] = FALSE;
+          }
         }
       }
 
@@ -341,6 +344,11 @@ class PollViewForm extends FormBase {
       // cache. When anonymous voting is allowed, the page cache should only
       // contain the voting form, not the results.
       $_SESSION['poll_vote'][$form_state->getValue('poll')->id()] = $form_state->getValue('choice');
+
+      // Set cookie to mark that user already voted.
+      if ($form_state->getValue('poll')->getVoteRestrictions() === 'cookie') {
+        user_cookie_save(['poll_voted' => $form_state->getValue('poll')->id()]);
+      }
     }
 
     // In case of an ajax submission, trigger a form rebuild so that we can
diff --git a/src/PollVoteStorage.php b/src/PollVoteStorage.php
index 93c7235..5e0ba65 100644
--- a/src/PollVoteStorage.php
+++ b/src/PollVoteStorage.php
@@ -63,6 +63,8 @@ class PollVoteStorage implements PollVoteStorageInterface {
    * {@inheritdoc}
    */
   public function cancelVote(PollInterface $poll, AccountInterface $account = NULL) {
+    $_SESSION['poll_vote'][$poll->id()] = FALSE;
+
     if ($account->id()) {
       $this->connection->delete('poll_vote')
         ->condition('pid', $poll->id())
@@ -121,6 +123,7 @@ class PollVoteStorage implements PollVoteStorageInterface {
    */
   public function getUserVote(PollInterface $poll) {
     $uid = \Drupal::currentUser()->id();
+
     if ($uid || $poll->getAnonymousVoteAllow()) {
       if ($uid) {
         $query = $this->connection->query("SELECT * FROM {poll_vote} WHERE pid = :pid AND uid = :uid", array(
@@ -129,13 +132,34 @@ class PollVoteStorage implements PollVoteStorageInterface {
         ));
       }
       else {
-        $query = $this->connection->query("SELECT * FROM {poll_vote} WHERE pid = :pid AND hostname = :hostname AND uid = 0", array(
-          ':pid' => $poll->id(),
-          ':hostname' => \Drupal::request()->getClientIp()
-        ));
+
+        switch ($poll->getVoteRestrictions()) {
+          case 'session':
+            return !empty($_SESSION['poll_vote'][$poll->id()]) ? $_SESSION['poll_vote'][$poll->id()] : FALSE;
+
+          case 'cookie':
+            // We don't have cookie on AJAX callback just after submit.
+            // So we need to check this in session first.
+            if (!empty($_SESSION['poll_vote'][$poll->id()])) {
+              return $_SESSION['poll_vote'][$poll->id()];
+            }
+
+            // Then let's check our cookie.
+            $poll_id = \Drupal::request()->cookies->get('Drupal_visitor_poll_voted');
+            return !empty($poll_id) && $poll_id === $poll->id();
+
+          case 'ip':
+          default:
+            $query = $this->connection->query("SELECT * FROM {poll_vote} WHERE pid = :pid AND hostname = :hostname AND uid = 0", array(
+              ':pid' => $poll->id(),
+              ':hostname' => \Drupal::request()->getClientIp()
+            ));
+            break;
+        }
       }
       return $query->fetchAssoc();
     }
+
     return FALSE;
   }
 
