diff --git a/README.md b/README.md
index 12dd3ffd5e46f267c812e2027cc4b64b888a1f8d..9b35ec2c7c177d73264180987be786b29cadfab8 100644
--- a/README.md
+++ b/README.md
@@ -173,21 +173,23 @@ In addition to the remember me function, you can turn on security images. Image
 
 `showFreshImage` - if set to true, the security image is fetched everytime user access the login page. Otherwise, it is stored in the cookie. Default `false`.
 
+`storageClass` - an implementation of `SimpleSAML\Module\campusmultiauth\Data\Storage`, default `SimpleSAML\Module\campusmultiauth\Data\DatabaseStorage`.
+
+`pictureStorage` - if some other storage than `SimpleSAML\Module\campusmultiauth\Data\DatabaseStorage` is used (e.g. `SimpleSAML\Module\campusmultiauth\Data\PerunStorage`), this is the place for the configuration of the storage.
+
+`security.cookie.path` - cookie path.
+
+`security.cookie.samesite` - cookie SameSite.
+
 `pictureDir` - if set, the security image is stored in this directory instead of the cookie. The cookie than contains only a link to the picture. Also, if this option is enabled, `securityImageSalt` and `pictureBaseURL` are mandatory. Default `null`.
 
 `securityImageSalt` - a salt which is used in the filename of the picture if the `pictureDir` is on.
 
 `pictureBaseURL` - base URL to the pictures if the `pictureDir` is on.
 
-`storageClass` - an implementation of `SimpleSAML\Module\campusmultiauth\Data\Storage`, default `SimpleSAML\Module\campusmultiauth\Data\DatabaseStorage`.
-
 `pictures_table` - name of the table with security images, default `security_image`.
 
-`pictureStorage` - if some other storage than `SimpleSAML\Module\campusmultiauth\Data\DatabaseStorage` is used (e.g. `SimpleSAML\Module\campusmultiauth\Data\PerunStorage`), this is the place for the configuration of the storage.
-
-`security.cookie.path` - cookie path.
-
-`security.cookie.samesite` - cookie SameSite.
+`texts_table` - default `alternative_text`. You can also add an alternative text to images. User can specify his/her own text, so this is an additional antiphishing feature. If user does not have the alternative text set, the alt is an empty string. In case he/she does not have the image set, this text will be displayed instead of it.
 
 ## Hinting
 
diff --git a/config-templates/module_campusmultiauth.php b/config-templates/module_campusmultiauth.php
index 81c68d766c7ef1d9a48e2820567368d25a3cd9da..d466b1ef90b2d6546bd5c29f502335e2f1b7eae3 100644
--- a/config-templates/module_campusmultiauth.php
+++ b/config-templates/module_campusmultiauth.php
@@ -140,6 +140,7 @@ $config = [
             //        'ldap.basedn' => '',
             //        'search.filter' => '',
             //        'attribute' => '',
+            //        'alternative_text_attribute' => '',
             //    ],
         ],
         //    'uidName' => '',
diff --git a/lib/Auth/Process/RememberMe.php b/lib/Auth/Process/RememberMe.php
index e801fe5446d35ae899b2f6a721a427a29b4ae8d7..74573d33ff2f531c6aef3dfb8b67708ee9d790c4 100644
--- a/lib/Auth/Process/RememberMe.php
+++ b/lib/Auth/Process/RememberMe.php
@@ -203,6 +203,7 @@ class RememberMe extends ProcessingFilter
 
         if (!$this->showFreshImage) {
             $payload['security_image'] = Utils::getSecurityImageOfUser($username);
+            $payload['alternative_text'] = Utils::getAlternativeTextOfUser($username);
         }
 
         $this->setCookie($payload);
diff --git a/lib/Data/DatabaseStorage.php b/lib/Data/DatabaseStorage.php
index 061c8d0145d248a8adf2d83d6cc2ee15d31ac3d8..be5d60f3c3e19101bf136fd915b871865a5804d5 100644
--- a/lib/Data/DatabaseStorage.php
+++ b/lib/Data/DatabaseStorage.php
@@ -22,6 +22,11 @@ class DatabaseStorage implements Storage
      */
     private $pictures_table;
 
+    /**
+     * DB table name for texts.
+     */
+    private $texts_table;
+
     /**
      * DB table name for tokens.
      */
@@ -48,9 +53,13 @@ class DatabaseStorage implements Storage
         $imagesConfiguration = $this->config->getConfigItem('security_images', []);
 
         $this->db = Database::getInstance($this->config->getConfigItem('store', []));
+
         $this->pictures_table = $this->db->applyPrefix(
             $imagesConfiguration->getString('pictures_table', 'security_image')
         );
+        $this->texts_table = $this->db->applyPrefix(
+            $imagesConfiguration->getString('texts_table', 'alternative_text')
+        );
         $this->tokens_table = $this->db->applyPrefix(
             $this->config->getString('tokens_table', 'cookie_counter')
         );
@@ -61,17 +70,17 @@ class DatabaseStorage implements Storage
      */
     public function getSecurityImageOfUser(string $uid): ?string
     {
-        $query = 'SELECT picture FROM ' . $this->pictures_table
-            . ' WHERE ' . self::UID_COL . '=:userid';
-        $statement = $this->db->read($query, [
-            'userid' => $uid,
-        ]);
-        $picture = $statement->fetchColumn();
-        if ($picture === false) {
-            return null;
-        }
+        $query = 'SELECT picture FROM ' . $this->pictures_table . ' WHERE ' . self::UID_COL . '=:userid';
+        return $this->getSecurityAttributeOfUser($uid, $query);
+    }
 
-        return $picture;
+    /**
+     * @override
+     */
+    public function getAlternativeTextOfUser(string $uid): ?string
+    {
+        $query = 'SELECT alternative_text FROM ' . $this->texts_table . ' WHERE ' . self::UID_COL . '=:userid';
+        return $this->getSecurityAttributeOfUser($uid, $query);
     }
 
     /**
@@ -140,4 +149,18 @@ class DatabaseStorage implements Storage
 
         return (bool) $this->db->write($query, $params);
     }
+
+    private function getSecurityAttributeOfUser(string $uid, string $query)
+    {
+        $statement = $this->db->read($query, [
+            'userid' => $uid,
+        ]);
+
+        $attribute = $statement->fetchColumn();
+        if ($attribute === false) {
+            return null;
+        }
+
+        return $attribute;
+    }
 }
diff --git a/lib/Data/PerunStorage.php b/lib/Data/PerunStorage.php
index ab7d44cb62db9e8b8214778ae8f3b0fdbda0cb8d..bfffdb9905bd80e6f6eb748b23da72040944b639 100644
--- a/lib/Data/PerunStorage.php
+++ b/lib/Data/PerunStorage.php
@@ -59,11 +59,25 @@ class PerunStorage extends DatabaseStorage
      * @override
      */
     public function getSecurityImageOfUser(string $uid): ?string
+    {
+        $attribute = $this->config->getString('attribute', null);
+        return $attribute === null ? null : $this->getSecurityAttributeOfUser($uid, $attribute);
+    }
+
+    /**
+     * @override
+     */
+    public function getAlternativeTextOfUser(string $uid): ?string
+    {
+        $attribute = $this->config->getString('alternative_text_attribute', null);
+        return $attribute === null ? null : $this->getSecurityAttributeOfUser($uid, $attribute);
+    }
+
+    private function getSecurityAttributeOfUser(string $uid, string $attribute)
     {
         $base = $this->config->getString('ldap.basedn');
         $filter = $this->config->getString('search.filter');
         $filter = str_replace('%uid%', $uid, $filter);
-        $attribute = $this->config->getString('attribute');
 
         try {
             $entries = $this->ldap->searchformultiple([$base], $filter, [$attribute], [], true, false);
diff --git a/lib/Data/Storage.php b/lib/Data/Storage.php
index da9f926648d2817c35076d5e8fe62a59ef448098..4ee55d64ef7004cf27087b5fb23390d33b7de5d8 100644
--- a/lib/Data/Storage.php
+++ b/lib/Data/Storage.php
@@ -13,6 +13,11 @@ interface Storage
      */
     public function getSecurityImageOfUser(string $uid): ?string;
 
+    /**
+     * Null if user has none, text otherwise.
+     */
+    public function getAlternativeTextOfUser(string $uid): ?string;
+
     /**
      * False if not found (should not happen), counter otherwise.
      */
diff --git a/lib/Utils.php b/lib/Utils.php
index ec4bfa6f9d52382f6e58f7766dc9e2eaa4157407..e36887e327843fb37bcb59df49068445b560bce8 100644
--- a/lib/Utils.php
+++ b/lib/Utils.php
@@ -29,4 +29,15 @@ class Utils
 
         return $storage->getSecurityImageOfUser($username);
     }
+
+    public static function getAlternativeTextOfUser($username)
+    {
+        $storage = self::getInterfaceInstance(
+            'SimpleSAML\\Module\\campusmultiauth\\Data\\Storage',
+            'storageClass',
+            'SimpleSAML\\Module\\campusmultiauth\\Data\\DatabaseStorage'
+        );
+
+        return $storage->getAlternativeTextOfUser($username);
+    }
 }
diff --git a/templates/includes/local-login.twig b/templates/includes/local-login.twig
index 1529c9425c1d1fc035e686984b50d08bf286d351..553e100cc2fc9a776037b55819bb2b84ecfc8998 100644
--- a/templates/includes/local-login.twig
+++ b/templates/includes/local-login.twig
@@ -13,7 +13,9 @@
     </h4>
 
     {% if securityImage is defined %}
-        <img src='{{ securityImage|escape }}' class='security-image' alt=''>
+        <img src='{{ securityImage|escape('html_attr') }}' class='security-image' alt='{{ alternativeText|default('')|escape('html_attr') }}'>
+    {% elseif alternativeText is defined %}
+        <p class="security-image-text">{{ alternativeText|escape }}</p>
     {% endif %}
 
     {% if wrongUserPass == true %}
diff --git a/www/selectsource.php b/www/selectsource.php
index 16aba55c86f0090e7d1c2f9981d8779972e2a8d3..bc2e18386cce901290096542e395c781b56a34be 100644
--- a/www/selectsource.php
+++ b/www/selectsource.php
@@ -343,14 +343,21 @@ if ($t->data['userInfo']) {
     if (empty($t->data['username']) || $t->data['userInfo']['username'] === $t->data['username']) {
         $t->data['username'] = $t->data['userInfo']['username'];
         $showFreshImage = $imagesConfig->getBoolean('showFreshImage', false);
+
         if ($showFreshImage && (($t->data['userInfo']['security_image'] ?? true) !== false)) {
             $t->data['securityImage'] = Utils::getSecurityImageOfUser($t->data['userInfo']['username']);
         } elseif (!$showFreshImage && !empty($t->data['userInfo']['security_image'])) {
             $t->data['securityImage'] = $t->data['userInfo']['security_image'];
         }
 
+        if ($showFreshImage && (($t->data['userInfo']['alternative_text'] ?? true) !== false)) {
+            $t->data['alternativeText'] = Utils::getAlternativeTextOfUser($t->data['userInfo']['username']);
+        } elseif (!$showFreshImage && !empty($t->data['userInfo']['alternative_text'])) {
+            $t->data['alternativeText'] = $t->data['userInfo']['alternative_text'];
+        }
+
         $pictureDir = $imagesConfig->getString('pictureDir', null);
-        if ($t->data['securityImage'] && $pictureDir !== null) {
+        if (!empty($t->data['securityImage']) && $pictureDir !== null) {
             $pictureDataSrc = $t->data['securityImage'];
             if (preg_match('~^data:image/(png|jpeg|gif);base64,(.*)$~', $pictureDataSrc, $matches)) {
                 list(, $pictureType, $pictureContent) = $matches;