misha-in / sso-client
Jednoduchý PHP klient pro integraci s SSO serverem pomocí OAuth 2.0 (Authorization Code + PKCE).
Requires
- php: >=8.1
- ext-curl: *
This package is not auto-updated.
Last update: 2026-07-31 00:02:32 UTC
README
Jednoduchý PHP klient pro integraci s SSO serverem id.produkce.ai pomocí OAuth 2.0 (Authorization Code + PKCE).
Nevyžaduje žádné závislosti – používá pouze PHP curl a cookies.
Požadavky
- PHP 8.1+
- Rozšíření
curl
Instalace
Composer (doporučeno)
composer require misha-in/sso-client
use MishaIn\SsoClient\SsoClient;
Bez Composeru
require_once __DIR__ . '/SsoClient.php';
Konfigurace
$sso = new SsoClient([
'client_id' => 'my-app', // ID klienta registrovaného na SSO
'client_secret' => 'tajny-klic', // Tajný klíč klienta
'redirect_uri' => 'https://app.example.com/callback', // Callback URL registrovaná na SSO
'sso_url' => 'https://id.produkce.ai', // Base URL SSO serveru
]);
Poznámka k scopům: Klient neposílá seznam požadovaných scopů. SSO server automaticky vrátí všechna oprávnění, která má přihlášený uživatel přiřazena pro danou aplikaci. Správa oprávnění probíhá výhradně na SSO serveru v administraci.
Použití
1. Přihlášení – přesměrování na SSO
$sso->redirectToLogin();
// Funkce ukončí skript přesměrováním (exit).
Interně se vygeneruje state (ochrana proti CSRF) a code_verifier (PKCE), které se uloží do
krátkodobých cookies (platnost 10 minut, SameSite=Lax).
2. Callback – výměna kódu za tokeny
Na callback URL (registrované na SSO) zpracujte parametry z $_GET:
try {
$tokens = $sso->handleCallback();
// $tokens['access_token'] – přístupový token (platnost 1 hodina)
// $tokens['refresh_token'] – obnovovací token (platnost 1 měsíc)
// $tokens['expires_in'] – platnost access tokenu v sekundách (3600)
// $tokens['token_type'] – "Bearer"
} catch (\MishaIn\SsoClient\SilentAuthenticationException $e) {
// Silent auth selhalo (uživatel není přihlášen na SSO)
// $e->errorCode === 'login_required' | 'interaction_required' | ...
} catch (RuntimeException $e) {
// Neplatný state (CSRF) nebo chyba serveru
die('Přihlášení selhalo: ' . $e->getMessage());
}
Tokeny jsou automaticky uloženy do $_SESSION – klient je spravuje automaticky.
3. Informace o uživateli
$user = $sso->getUserInfo($tokens['access_token']);
// $user['sub'] – ID uživatele (int)
// $user['email'] – e-mailová adresa
// $user['name'] – celé jméno
// $user['scopes'] – pole přidělených oprávnění, např. ['dashboard', 'articles.faq']
// $user['role'] – per-klientská role uživatele (vždy vyplněna;
// bez přiřazené role uživatel ověřením neprojde)
// $user['role']['id'] – číselné ID role (int)
// $user['role']['name'] – název role (string), např. 'admin', 'manager', 'user'
// $user['role']['rank'] – pořadí v hierarchii rolí (int); nižší číslo = vyšší oprávnění
//
// Pokud byl uživateli odebrán přístup (smazána role), SSO vrátí chybu a
// getUserInfo() vyhodí RuntimeException – zachyťte ji stejně jako expiraci tokenu.
Scopy jsou ve formátu dot-notace: parent.child. Scope bez rodiče je jen identifier (např. dashboard).
4. Kontrola oprávnění (scope)
if ($sso->hasScope('articles.faq')) {
// uživatel má přidělený přesně scope "articles.faq"
}
Metoda podporuje wildcard suffix .* pro kontrolu celé větve stromu scopů:
$sso->hasScope('dashboard') // true – pouze přesná shoda "dashboard"
$sso->hasScope('rag') // false – kategorie má děti, je tedy nutná kontrola s hvězdičkou
$sso->hasScope('rag.*') // true – uživatel má jakýkoli scope začínající "rag." (např. "rag.faq.edit")
$sso->hasScope('rag.faq.*') // true – uživatel má jakýkoli scope začínající "rag.faq."
$sso->hasScope('rag.faq') // false – přesná shoda; "rag.faq.edit" nestačí
| Volání | Scope uživatele | Výsledek |
|---|---|---|
hasScope('rag') | rag.faq.edit | false |
hasScope('rag') | rag | true |
hasScope('rag.*') | rag.faq.edit | true |
hasScope('rag.faq.*') | rag.faq.edit | true |
hasScope('rag.faq') | rag.faq.edit | false |
Metoda interně zavolá getUserInfo() – doporučuje se výsledek cachovat na úrovni aplikace
(např. ukládat $user do session po dobu přihlášení).
5. Práce s rolí
Role uživatele pro danou aplikaci je vždy přítomna – uživatel bez přiřazené role
neprojde ověřením a volání getUserInfo() vyhodí RuntimeException (stejně jako
expirovaný token). Aplikace nikdy nedostane data o uživateli bez přiřazené role.
Platí pro každého, včetně administrátorů – pokud mají mít přístup, musí mít roli přiřazenou.
try {
$user = $sso->getUserInfo($accessToken);
} catch (\RuntimeException $e) {
// Token expiroval, nebo byla uživateli odebrána role pro tuto aplikaci.
// Zacházejte stejně jako s vypršenou session.
$sso->redirectToLogin();
}
$role = $user['role']; // array{id: int, name: string, rank: int}
echo $role['name']; // 'admin' | 'manager' | 'user' | ... (název definovaný v administraci)
echo $role['id']; // 5 – numerické ID role (stabilní, vhodné pro strojové porovnání)
echo $role['rank']; // 1 – pořadí v hierarchii rolí; nižší číslo = vyšší oprávnění
rank umožňuje aplikaci porovnávat oprávnění hierarchicky, aniž by musela znát konkrétní
názvy rolí:
// Vpustit jen uživatele s dostatečně vysokou rolí (rank 1 nebo 2)
if ($role['rank'] <= 2) {
// přístup povolen
}
Pořadí rolí (rank) nastavuje správce aplikace přes administraci SSO drag & dropem.
Poznámka: Pokud je role uživateli odebrána po vydání access tokenu, SSO zablokuje přístup při nejbližším volání
/userinfo– aplikace tedy nepracuje se zastaralými daty.
6. Ověření přihlášení
if (!$sso->isAuthenticated()) {
$sso->redirectToLogin();
}
Metoda automaticky obnoví access token pomocí refresh tokenu, pokud vypršel. Pokud obnova selže
(refresh token expiroval), vrátí false.
7. Platný access token
Pro přímou práci s tokenem (např. vlastní API volání):
$accessToken = $sso->getValidAccessToken();
if ($accessToken === null) {
$sso->redirectToLogin(); // session vypršela
}
8. Obnovení tokenu
Access token se obnovuje automaticky při volání isAuthenticated() a getValidAccessToken().
Pokud potřebujete obnovit token ručně:
try {
$newTokens = $sso->refreshToken($refreshToken);
} catch (RuntimeException $e) {
// Refresh token vypršel – přesměrujte na přihlášení
$sso->redirectToLogin();
}
9. Odhlášení
$sso->logout();
// nebo s přesměrováním po odhlášení:
$sso->logout('https://app.example.com/');
Pro sestavení URL bez okamžitého přesměrování (např. odkaz v šabloně):
$logoutUrl = $sso->getLogoutUrl('https://app.example.com/');
10. Seznam uživatelů aplikace
Endpoint /users vrací seznam všech uživatelů, kteří mají k dané aplikaci přiřazenu roli.
Přístup je chráněn Bearer tokenem – token musí patřit k dané aplikaci (client_id).
$accessToken = $sso->getValidAccessToken();
// Všichni uživatelé aplikace
$users = $sso->getUsers($accessToken);
// Pouze uživatelé s rolí 'admin'
$admins = $sso->getUsers($accessToken, role: 'admin');
// Pouze uživatelé mající přiřazen scope 'articles.edit'
$editors = $sso->getUsers($accessToken, scope: 'articles.edit');
// Kombinace filtrů – administrátoři mající scope 'reports'
$result = $sso->getUsers($accessToken, role: 'admin', scope: 'reports');
Každý prvek vráceného pole má tvar:
[
'sub' => 42, // int – interní ID uživatele (stejné jako v /userinfo)
'name' => 'Jan Novák', // string – celé jméno
'email' => 'jan@firma.cz', // string – e-mail
]
Poznámka: Vrací se vždy jen uživatelé dané aplikace. Token vystavený pro jiného klienta nemůže získat uživatele vaší aplikace.
Silent Authentication (OIDC prompt=none)
Pokud lokální session vypršela, ale uživatel může stále být přihlášen na SSO serveru, loní lze obnovit bez jakékoliv interakce uživatele.
SSO server při prompt=none nikdy nezobrazí HTML formulář – buď vrátí authorization code
(uživatel přihlášen), nebo přesměruje zpět s error=login_required.
// 1. Inicializace tichého ověření (místo redirectToLogin())
$sso->attemptSilentAuthentication(); // přesměrování na SSO s prompt=none
// 2. Na callback URL
try {
$tokens = $sso->handleCallback();
// Úspěch – uživatel byl přihlášen na SSO, lokální session obnovena
} catch (\MishaIn\SsoClient\SilentAuthenticationException $e) {
// Očekávaný stav: uživatel není přihlášen na SSO
// $e->errorCode může být:
// 'login_required' – není přihlášen
// 'interaction_required' – SSO vyžaduje interakci
// 'consent_required' – musí udělit souhlas
// 'account_selection_required' – musí vybrat účet
// → přesměrujte na běžný login, nebo zobrazte veřejnou stránku
} catch (\RuntimeException $e) {
// Skutečná chyba (CSRF, network, server error)
}
Tip: Ukladaní
sso_auth_hintcookie při úspěšném přihlášení umožňuje rozhodnout, zda tichý pokus smá zahajovat (zamezí neřízeným redirect smyčkám).
Kompletní příklad integrace
<?php
session_start();
require_once __DIR__ . '/SsoClient.php';
use MishaIn\SsoClient\SsoClient;
$sso = new SsoClient([
'client_id' => 'my-app',
'client_secret' => 'tajny-klic',
'redirect_uri' => 'https://app.example.com/callback',
'sso_url' => 'https://id.produkce.ai',
]);
$action = $_GET['action'] ?? '';
// Přihlášení
if ($action === 'login') {
$sso->redirectToLogin();
}
// Callback ze SSO
if ($action === 'callback') {
try {
$sso->handleCallback();
header('Location: /dashboard');
exit;
} catch (\MishaIn\SsoClient\SilentAuthenticationException $e) {
// Tiché ověření selhalo – uživatel není přihlášen na SSO
header('Location: /');
exit;
} catch (RuntimeException $e) {
die('Chyba přihlášení: ' . $e->getMessage());
}
}
// Odhlášení
if ($action === 'logout') {
$sso->logout('https://app.example.com/');
}
// Chráněná stránka
if (!$sso->isAuthenticated()) {
$sso->redirectToLogin();
}
$accessToken = $sso->getValidAccessToken();
$user = $sso->getUserInfo($accessToken);
echo 'Přihlášen jako: ' . htmlspecialchars($user['name']);
echo 'Oprávnění: ' . implode(', ', $user['scopes']);
echo 'Role: ' . htmlspecialchars($user['role']['name']) . ' (rank: ' . $user['role']['rank'] . ')';
// Kontrola konkrétního oprávnění
if ($sso->hasScope('articles.faq')) {
echo 'Má přístup k FAQ.';
}
Přehled metod
| Metoda | Návratový typ | Popis |
|---|---|---|
redirectToLogin(?string $prompt = null): never | – | Přesměruje uživatele na SSO přihlašovací stránku; $prompt='none' = bez UI |
attemptSilentAuthentication(): never | – | Tiché ověření přihlášení (OIDC prompt=none) |
handleCallback(): array | tokeny | Vymění authorization code za tokeny; při silent auth chybě vyhodí SilentAuthenticationException |
isAuthenticated(): bool | bool | Vrátí true pokud je uživatel přihlášen (automaticky obnoví token) |
getValidAccessToken(): ?string | string|null | Vrátí platný access token nebo null pokud session vypršela |
getUserInfo(string $accessToken): array | user array | Vrátí data uživatele vl. polí scopes a role (id, name, rank), nebo null |
getUsers(string $accessToken, ?string $role = null, ?string $scope = null): array | list uživatelů | Vrátí seznam uživatelů aplikace; volitelně filtruje dle role nebo scopu |
hasScope(string $scope): bool | bool | Zkontroluje, zda má uživatel dané oprávnění |
refreshToken(string $refreshToken): array | tokeny | Ručně obnoví access token |
isTokenValid(string $accessToken): bool | bool | Ověří platnost tokenu dotazem na SSO |
getLogoutUrl(?string $redirectUri = null): string | string | Vrátí URL pro odhlášení (bez přesměrování) |
logout(?string $redirectUri = null): never | – | Odhlásí uživatele a přesměruje na SSO |
Formát scopů
Oprávnění jsou ve formátu dot-notace odpovídající hierarchii definované na SSO serveru:
dashboard – přístup k dashboardu
rag.faq – přístup k faq v rámci rag
rag.files – přístup k files v rámci rag
rag.prompt – přístup k prompt v rámci rag
Každý uživatel má oprávnění spravována administrátorem přímo na SSO serveru. Aplikace scopy nežádá – dostane automaticky vše, co má daný uživatel přiřazeno.
Bezpečnostní poznámky
- CSRF ochrana:
stateparametr je generován pomocírandom_bytes(32)a ověřován přeshash_equals(). - PKCE: Každý login flow používá unikátní
code_verifier(S256) – chrání i při úniku authorization code. - Tajný klíč (
client_secret) nikdy neopouští server – nikdy ho nevkládejte do front-endového kódu nebo do verzovacího systému. - Tokeny jsou ukládány do
$_SESSIONna straně serveru.