Document toolboxDocument toolbox

Možnosti ukončení SSO sezení z chráněné aplikace

Pro chráněné aplikace napojené na 2Element pomocí OpenID Connect existuje několik možností jak se zapojit do procesu ukončení SSO (Single Sign-On) sezení.

ID Token získaný během procesu ověření může obsahovat claim sid obsahující identifikátor SSO sezení. Chráněná aplikace by měla uložit sid anebo celý ID Token pro budoucí použití v rámci odhlášení. Pokud ID Token neobsahuje claim sid, přihlášení neproběhlo v rámci systému jednotného přihlášení (SSO) a následující metody není možné použít.

Aplikace 2Element implementuje následující OpenID specifikace:

OpenID Connect RP-Initiated Logout 1.0

URL: https://openid.net/specs/openid-connect-rpinitiated-1_0.html

Tato specifikace umožňuje chráněné aplikaci (Relying Party) požádat aplikaci 2Element o ukončení SSO sezení.

Provede to přesměrováním uživatele na URL uvedené jako End Session Endpoint URI v detailu chráněné aplikace. K URL musí být přidán parametr client_id obsahující hodnotu Client ID z detailu chráněné aplikace a parametr id_token_hint obsahující ID token získaný při ověření uživatele. Pokud chráněná aplikace vyžaduje přesměrování uživatele zpět po provedeném odhlášení, musí přidat parametr post_logout_redirect_uri s URL na které bude uživatel po odhlášení přesměrován. Zadané URL musí být povolené v detailu chráněné aplikace, podobně jako parametr redirect_uri používaný při ověřování uživatele.

Po otevření End Session Endpoint URI v prohlížeči je uživateli zobrazena odhlašovací stránka kde uživatel musí odhlášení (ukončení SSO sezení) potvrdit. Po potvrzení dojde i k odhlášení od všech aplikací které to podporují (tj. implementují Front-Channel nebo Back-Channel Logout).

Uživatele je možné z chráněné aplikace lokálně odhlásit před přesměrováním na End Session Endpoint URI. Uživatele je také možné lokálně neodhlašovat a spoléhat na to že aplikace 2Element provede odhlášení sama pomocí mechanismů Front-Channel nebo Back-Channel Logout. Uživatel ale může ukončení SSO sezení odmítnout nebo ignorovat - chráněná aplikace se o tom již nedozví a uživatel zůstane přihlášen.

OpenID Connect Front-Channel Logout 1.0

URL: https://openid.net/specs/openid-connect-frontchannel-1_0.html

Tato specifikace umožňuje aplikaci 2Element odhlásit uživatele z chráněné aplikace pomocí speciální odhlašovací stránky. V detailu chráněné aplikace je nutné nastavit parametr URL pro odhlášení dopředným kanálem který obsahuje stránku chráněné aplikace která při načtení uživatele odhlásí. Aplikace 2Element k tomuto URL přidá parametr iss obsahující název vydavatele a parametr sid obsahující identifikátor SSO sezení. Oba parametry jsou uvedeny jako claim v ID Tokenu získaném během ověření uživatele.

Na odhlašovací stránce aplikace 2Element je pro každou chráněnou aplikaci vykreslen neviditelný rámec (iframe) se zdrojem nastaveným na hodnotu parametru URL pro odhlášení dopředným kanálem, s přidanými parametry iss a sid. Tím dojde k nahrání odhlašovacích stránek v prohlížeči a následně i k odhlášení uživatele z chráněných aplikací.

Pomocí atributu SameSame je možné v HTTP cookie omezit přístup ke cookie z jiných domén při nahrávání obsahu pomocí XHR nebo vloženého rámce (iframe). Cílem atributu je zamezit XSRF (Cross-Site Scripting Forgery) útokům. Atribut může nabývat tří hodnot - None, Lax a Strict. Jedině nastavení None dovolí prohlížeči odesílat cookie v rámci XHR požadavků nebo z iframe při přístupu z jiných domén. Z bezpečnostních důvodů je obecně doporučeno u session cookies nastavovat hodnotu na Lax a pokud není atribut nastaven, určí prohlížeče jako výchozí hodnotu stejně tak Lax. V důsledku toho není při načítání URL pro odhlášení dopředným kanálem v iframe prohlížečem odeslána session cookie a chráněná aplikace tak nemá informaci o přihlášeném uživateli.

Více informací o atributu SameSite je např. na adrese https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite.

Tento problém je možné obejít nastavením atributu SameSite na None s vědomím toho že dojde ke snížení bezpečnosti. Další možností je držet informaci o uživatelském sezení kromě HTTP cookie ještě v dalším úložišti (např. v databázi) a uživatele odhlašovat jen na základě parametrů iss a sid. V tom případě ale může kdokoli se znalostí ID sezení odhlásit uživatele z libovolné chráněné aplikace. Lepším řešením je implementace specifikace Back-Channel Logout.

Oproti metodě Back-Channel Logout nevyžaduje metoda Front-Channel Logout otevřený HTTP port pro aplikaci 2Element a vystačí si s HTTP portem pro webové prohlížeče uživatelů. To může být důležité v případě že je chráněná aplikace umístěná v uzavřeném prostředí, nedostupném zvenčí.

V současné době prohlížeče Firefox a Safari blokují nebo izolují cookie třetích stran ve snaze zamezit sledování uživatelů pomocí cookies. U prohlížeče Chrome se předpokládá podobné chování od roku 2024. V důsledku toho není při načítání URL pro odhlášení dopředným kanálem v iframe prohlížečem odeslána session cookie a chráněná aplikace tak nemá informaci o přihlášeném uživateli.

Více informací je možné najít např. na adresách https://support.mozilla.org/en-US/kb/introducing-total-cookie-protection-standard-mode a https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/.

Tento problém je možné obejít úpravou nastavení prohlížeče u všech uživatelů. Dalším řešením je implementace specifikace Back-Channel Logout která tímto problémem netrpí.

OpenID Connect Back-Channel Logout 1.0

URL: https://openid.net/specs/openid-connect-backchannel-1_0.html

Tato specifikace umožňuje aplikaci 2Element odhlásit uživatele z chráněné aplikace pomocí speciálního HTTP volání. V detailu chráněné aplikace je nutné nastavit parametr URL pro odhlášení postranním kanálem který obsahuje HTTP adresu která umožní odhlásit uživatele z chráněné aplikace. Na tuto adresu je pomocí volání POST odeslán Logout Token obsahující mimo jiné JWT claim iss a sid. Logout Token je velmi podobný ID Tokenu používaném při procesu ověření a je podepsán stejným klíčem - nesmí ale obsahovat claim nonce aby ho nebylo možné s ID Tokenem zaměnit. Po obdržení požadavku chráněná aplikace uživatele odhlásí.

Back-Channel Logout klade větší nároky na implementaci na straně chráněné aplikace protože pro držení informace o sezení nestačí session cookie. Během požadavku mezi aplikací 2Eelement a chráněnou aplikací nejsou chráněné aplikaci k dispozici HTTP cookies uživatele a tak musí informaci o sezení držet ještě v dalším úložišti (např. v databázi).

Narozdíl od Front-Channel Logout není funkčnost omezena blokováním HTTP cookies v prohlížeči a jde proto o spolehlivější metodu. Naproti tomu vyžaduje otevřený HTTP port pro přijímání požadavků od aplikace 2Element což může být problém v uzavřeném prostředí.

OpenID Connect Session Management 1.0

URL: https://openid.net/specs/openid-connect-session-1_0.html

Tato specifikace umožňuje chráněné aplikaci sledovat stav sezení v rámci jednotného přihlášení (SSO) a reagovat v případě změn. Jedinou podporovanou změnou v aplikaci 2Element je zrušení platnosti SSO sezení (odhlášení).

Změna stavu sezení je sledována pomocí pravidelného zasílání zpráv chráněnou aplikací do rámce (iframe) vloženého z aplikace 2Element. Zdrojová adresa rámce je zobrazena v detailu chráněné aplikace jako Check Session IFrame URI. Pro správnou funkci CSP (Content Security Policy) hlaviček je nutné k URL přidat parametr client_id obsahující hodnotu zobrazenou jako parametr Client ID v detailu chráněné aplikace.

Zpráva vloženému rámci je text ve formátu “<client_id> <session_state>“. Parametr session_state je vrácen v odpovědi na ověření uživatele, vedle parametrů code a state (není součástí ID Tokenu).

Rámec aplikace 2Element odpovídá pomocí zprávy odeslané odesílajícímu rámci (oknu):

  • textem “unchanged” pokud je sezení stále platné

  • textem “changed” pokud už sezení není platné

  • textem “error” v případě že zaslaná zpráva nebyla ve správném formátu

V případě odpovědi “changed” může chráněná aplikace uživatele lokálně odhlásit a přesměrovat ho na přihlašovací stránku aplikace 2Element.

Rámec (nebo okno) chráněné aplikace musí u přijatých zpráv kontrolovat Origin pro zamezení XSS (Cross-Site Scripting) zranitelností. Rámec aplikace 2Element kontroluje Origin oproti hodnotám zadaným jako URI pro přesměrování v detailu chráněné aplikace (ze zadaných hodnot je odvozen Origin podle RFC 6454).

Specifikace počítá s tím že poskytovatelé identit jako je aplikace 2Element použijí pro uložení stavu o přihlášení HTTP cookies - to je ale vzhledem k blokování cookies v moderních prohlížečích často nemožné (viz. sekce o Front-Channel Logout). Implementace v aplikaci 2Element proto cookies nepoužívá a místo toho volá pomocný HTTP endpoint pro každou přijatou zprávu. Velikost dat tohoto volání je velmi malá a volání je nenáročné na výkon serveru, je proto možné ho volat poměrně často (např. každou minutu).

Specifikace počítá s použitím dvou vložených rámců - jednoho rámce pro chráněnou aplikaci a jednoho pro poskytovatele identity (aplikaci 2Element). Použití dvou rámců ve spojení s aplikací 2Element není nutné a v tuto chvíli nepřináší žádnou výhodu.

Metoda Session Management může být použita samostatně nebo kombinována s metodami Front-Channel a Back-Channel Logout. Narozdíl od ostatních metod může být také použita v SPA (Single Page Application) nebo nativních aplikacích protože nevyžaduje backend.

Nejjednodušší možná implementace na straně webové chráněné aplikace může vypadat např. takto:

<html lang="en"> <body> <p>session check result: <span id="result">N/A</span></p> <script type="application/javascript"> const PROVIDER_URL = '__PROVIDER_URL__'; const SESSION_STATE = '__SESSION_STATE__'; const CLIENT_ID = '__CLIENT_ID__'; const iframe = document.createElement('iframe'); iframe.src = `${PROVIDER_URL}/api/sso/oidc/check-session-iframe?client_id=${CLIENT_ID}`; iframe.style.display = 'none'; document.body.appendChild(iframe); function onMessage(e) { if (e.origin !== PROVIDER_URL) { console.debug("Invalid origin: ", e.origin, " vs. ", PROVIDER_URL); return; } document.getElementById('result').innerText = e.data; } function checkSessionState() { iframe.contentWindow.postMessage(CLIENT_ID + ' ' + SESSION_STATE, PROVIDER_URL); } window.addEventListener('message', onMessage, false); setInterval(checkSessionState, 10 * 1000) </script> </body> </html>