diff --git a/classes/shibd_discofeed.class.php b/classes/shibd_discofeed.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..6323d812eaf36181ad04cfa82a5d2c8e9dda5336
--- /dev/null
+++ b/classes/shibd_discofeed.class.php
@@ -0,0 +1,101 @@
+<?php
+
+class shibd_discofeed
+{
+    protected string $SELFURL = '';
+    protected string $listcache = 'discofeed.json';
+    protected string $url_list = '/Shibboleth.sso/DiscoFeed';
+    protected string $url_login = '/Shibboleth.sso/Login';
+    protected int $ttlcache = 60*60;
+
+    protected string $configfile = 'config.php';
+    protected array $aConfig = [];
+
+    // ----------------------------------------------------------------------
+
+    public function __construct(string $SELFURL = '')
+    {
+        $SELFURL = $SELFURL ?: (
+            isset($_SERVER['SERVER_NAME']) ? "https://" . $_SERVER['SERVER_NAME'] : ''
+        );
+
+        if(!$SELFURL) {
+            die("ERROR: SELFURL is not set. \$_SERVER['SERVER_NAME'] is not available.");
+        }
+
+        $this->SELFURL = $SELFURL;
+        $this->url_list = "$SELFURL$this->url_list";
+        $this->url_login = "$SELFURL$this->url_login";
+
+        $this->listcache = dirname(__DIR__).'/'.$this->listcache;
+        $this->configfile = dirname(__DIR__).'/'.$this->configfile;
+
+        // get the user config
+        if (!file_exists($this->configfile)) {
+            die("ERROR: config.php file does not exist yet.");
+        }
+
+        $this->aConfig = require($this->configfile);
+    }
+
+    /**
+     * Get List if IDPs from cache file if possible 
+     * or from Shibboleth Disco feed and write a cache file
+     * @return array
+     */
+    function getAllIdps(): array
+    {
+        if (!file_exists($this->listcache) || filemtime($this->listcache) < time() - $this->ttlcache) {
+            // echo "DEBUG: IDP - reading from Shibboleth<br>";
+            $aReturn = json_decode(file_get_contents($this->url_list), 1);
+    
+            if ($aReturn && is_array($aReturn)) {
+                // echo "DEBUG: IDP - storing cache<br>";
+                file_put_contents($this->listcache, json_encode($aReturn));
+            }
+        } else {
+            // echo "DEBUG: IDP - reading cache<br>";
+            $aReturn = json_decode(file_get_contents($this->listcache), 1);
+        }
+    
+        return isset($aReturn) && is_array($aReturn) ? $aReturn : [];
+    }
+
+    /**
+     * Get list of active IDPs
+     * @return mixed
+     */
+    public function getIdps(): array
+    {
+        $aAllIdps = $this->getAllIdps();
+
+        $aReturn = [];
+        if (is_array($aAllIdps) && count($aAllIdps)) {
+            foreach ($aAllIdps as $aEntry) {
+                $sEntityId = $aEntry['entityID'];
+    
+                if (in_array($sEntityId, $this->aConfig['idps'])) {
+    
+                    $sLabel = $aEntry['DisplayNames'][0]['value'] ?? parse_url($sEntityId, PHP_URL_HOST);
+                    $sImage = $aEntry['Logos'][1]['value'] ?? ($aEntry['Logos'][0]['value'] ?? '');
+    
+                    $sUrl = $this->url_login
+                        . '?entityID='. urlencode($sEntityId) 
+                        . "&target=" . urlencode($sEntityId) 
+                        . "&target=" . urlencode($this->SELFURL.($aConfig['return-url']??''))
+                        ;
+    
+                    $aReturn[] = [
+                        'label' => $sLabel,
+                        'image' => $sImage,
+                        'url' => $sUrl,
+    
+                        // for debugging
+                        '_entity' => $aEntry
+                    ];
+                }
+            }
+        }
+        return $aReturn;
+    }
+}
\ No newline at end of file