auth.stromcom.cz — návod pro vývojáře
Tenhle dokument popisuje, jak napojit svoji aplikaci na náš centrální SSO/OAuth 2.0 server. Cíl: přihlášení přes auth.stromcom.cz → tvoje aplikace dostane JWT → ověří podpis přes JWKS → ví, kdo je uživatel a jaké má skupiny.
Pokud potřebuješ jen krátký Q&A bez čtení: skoč na Cheat sheet na konci.
Jak to funguje
┌────────────────┐ ┌───────────────────────┐
│ Tvoje appka │ ── redirect ──> │ auth.stromcom.cz │
│ (translate, │ │ - login form │
│ deploy, ...) │ <── code ──── │ - vystavuje JWT │
│ │ │ - JWKS /.well-known │
│ │ ── code+secret ─>│ │
│ │ <── JWT tokens ──│ │
│ │ │ │
│ │ GET /api/x │ │
│ middleware ──┼──────────────────┼───────────────────────┘
│ verify JWT │ (JWKS cache 1h)
│ ✓ user known │
└────────────────┘
Tvoje aplikace nikdy nevidí heslo uživatele. Vidí jen podepsaný JWT s claimy
sub, email, name, groups, is_admin. Podpis ověří lokálně (cache JWKS) — žádné
volání na auth serveru per request.
Kdy použít co
| Use case | Flow | Token typ |
|---|---|---|
| Web app s uživatelským loginem | authorization_code + PKCE | token_use=user |
| SPA / mobile s uživatelským loginem | authorization_code + PKCE (public klient) | token_use=user |
| Backend → backend / API klient / CI / cron | client_credentials | token_use=service |
| Server-to-server vyžadující identitu konkrétního uživatele | passthrough JWT (klient pošle headerem dál) | token_use=user |
Pokud máš jakoukoliv pochybnost: chce to autentizovat člověka? → authorization_code. Autentizuje se stroj sám sebou? → client_credentials.
1. Registrace klienta
Klienta musí založit admin v UI nebo přes CLI.
V admin UI
https://auth.stromcom.cz/admin/clients → vyplnit form:
- Web app / SPA: nezatrhávej "Service account", vyplň redirect URI (URL kam tě má auth
server přesměrovat po loginu, typicky
https://moje-app.stromcom.cz/oauth/callback). - Service account / API: zatrhni "🤖 Service account", vyber groups.
Po vytvoření se ti jednou zobrazí client_id a client_secret. Ulož si je hned.
Secret se znovu nezobrazí — kdyby ses ztratil, musíš klienta revokenout a vytvořit nového.
Z CLI
# Web app
composer client:create -- \
--name=translate-server \
--redirect-uri=https://translate.stromcom.cz/oauth/callback
# Service account / API
composer client:create -- \
--name=ci-bot \
--service-account \
--group=deployer \
--group=reader
2. Authorization Code flow (uživatelský login)
Standardní OAuth 2.0 + PKCE. 5 kroků, klient řeší 4 z nich.
2.1 Redirect na auth server
Když uživatel přijde na chráněnou stránku a nemá session, přesměruj ho na:
GET https://auth.stromcom.cz/oauth/authorize?
response_type=code
&client_id=cli_xxxxxxxxxxxxxxxx
&redirect_uri=https://moje-app.stromcom.cz/oauth/callback
&scope=openid profile email groups
&state=<náhodný_csrf_token>
&code_challenge=<base64url_sha256_of_verifier>
&code_challenge_method=S256
Co je co:
state— náhodný string, uložíš si ho v session. Auth server ho vrátí v callbacku; pokud nesedí, jde o CSRF, zahoď request.code_challenge/verifier— PKCE. Vygeneruj náhodnýcode_verifier(43–128 znaků), spočítejsha256+ base64url, pošli challenge. Verifier si schovej v session.
// Vygenerování PKCE pairu v PHP
$verifier = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
$challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');
$_SESSION['oauth_verifier'] = $verifier;
$_SESSION['oauth_state'] = bin2hex(random_bytes(16));
2.2 Uživatel se přihlásí na auth.stromcom.cz
Auth server vyrenderuje login formulář, uživatel zadá email + heslo, auth ho přihlásí.
2.3 Auth server vrátí code
GET https://moje-app.stromcom.cz/oauth/callback?code=def_xxxxxxxxxxxx&state=<tvůj_state>
Tvoje aplikace v handleru /oauth/callback:
- Ověř
stateproti uloženému (CSRF). - Vyměn
codeza tokeny.
2.4 Výměna code za tokeny
curl -X POST https://auth.stromcom.cz/oauth/token \
-d grant_type=authorization_code \
-d client_id=cli_xxxxxxxxxxxxxxxx \
-d client_secret=... \
-d redirect_uri=https://moje-app.stromcom.cz/oauth/callback \
-d code=def_xxxxxxxxxxxx \
-d code_verifier=<verifier_ze_session>
Odpověď:
{
"token_type": "Bearer",
"expires_in": 900,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjFhMmIzYzRkNWU2ZjdhOGIifQ...",
"refresh_token": "def50200..."
}
2.5 Ulož tokeny
// Doporučeně: httpOnly Secure SameSite=Lax cookies, expirace odpovídající tokenu.
setcookie('access_token', $tokens['access_token'], [
'expires' => time() + $tokens['expires_in'],
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
setcookie('refresh_token', $tokens['refresh_token'], [
'expires' => time() + 14*24*3600, // 14 dní
'path' => '/oauth/refresh', // jen sem se posílá
'secure' => true,
'httponly' => true,
'samesite' => 'Strict',
]);
2.6 Refresh (když access token vyprší)
curl -X POST https://auth.stromcom.cz/oauth/token \
-d grant_type=refresh_token \
-d client_id=cli_xxxxxxxxxxxxxxxx \
-d client_secret=... \
-d refresh_token=def50200...
Vrátí nový pár (rotace). Starý refresh token je hned revokovaný. Pokud volání selže
401, pošli uživatele znovu na /oauth/authorize (login expiroval / byl revokovaný).
3. Client Credentials flow (machine-to-machine)
Service account flow je jednoduší — žádný browser, žádný code, jeden HTTP call.
curl -X POST https://auth.stromcom.cz/oauth/token \
-d grant_type=client_credentials \
-d client_id=svc_xxxxxxxxxxxxxxxx \
-d client_secret=...
Odpověď:
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiI..."
}
Žádný refresh token — když token vyprší, pošli nový request. TTL = 1 hodina.
Pozor: client_secret patří do secret manageru (SSM, AWS Secrets Manager,
GitHub secrets). Nikdy ne do repa, nikdy do client-side kódu.
4. Ověření JWT v aplikaci
Tvoje aplikace dostala access_token (JWT podepsaný RS256). Co s ním:
4.1 Stáhnout veřejný klíč (JWKS)
curl https://auth.stromcom.cz/.well-known/jwks.json
{
"keys": [{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "1a2b3c4d5e6f7a8b",
"n": "uIJ-t5T2B116w6p0M0yh...",
"e": "AQAB"
}]
}
Cachuj výsledek na 1 hodinu (server posílá Cache-Control: max-age=3600). Při rotaci
klíčů auth server přidá nový kid do JWKS, klient si ho stáhne při příští expiraci cache.
4.2 Ověř podpis a expiraci
Použij hotovou knihovnu pro tvůj jazyk — nikdy nepiš JWT verifikaci ručně.
PHP (web-token/jwt-framework nebo firebase/php-jwt)
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
// Cache JWKS na 1h
$jwks = json_decode(file_get_contents('https://auth.stromcom.cz/.well-known/jwks.json'), true);
$keys = JWK::parseKeySet($jwks);
try {
$decoded = JWT::decode($accessToken, $keys);
// $decoded->sub, $decoded->email, $decoded->groups, $decoded->is_admin, $decoded->token_use
} catch (\Throwable $e) {
// expired / invalid signature / unknown kid → redirect na login
}
Node.js (jose)
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(new URL('https://auth.stromcom.cz/.well-known/jwks.json'));
try {
const { payload } = await jwtVerify(accessToken, JWKS, {
issuer: 'https://auth.stromcom.cz',
});
// payload.sub, payload.email, payload.groups, payload.is_admin, payload.token_use
} catch (e) {
// invalid → redirect
}
Python (PyJWT + cryptography)
import jwt
from jwt import PyJWKClient
jwks_client = PyJWKClient("https://auth.stromcom.cz/.well-known/jwks.json")
signing_key = jwks_client.get_signing_key_from_jwt(access_token)
data = jwt.decode(access_token, signing_key.key, algorithms=["RS256"], issuer="https://auth.stromcom.cz")
# data["sub"], data["email"], data["groups"], data["is_admin"], data["token_use"]
4.3 Co kontrolovat
| Claim | Co znamená | Co s tím |
|---|---|---|
iss |
Issuer — vždy https://auth.stromcom.cz. |
Knihovna ověří automaticky, když jí předáš issuer. |
exp |
Expirace. User token = 15 min, service token = 1h. | Knihovna ověří automaticky. |
aud |
Client ID, pro který byl token vystaven. | Pokud máš víc klientů, ověř že je to ten tvůj. |
sub |
User ID nebo client ID (service token). | Identita subjektu — použij pro lookup v lokální DB. |
token_use |
user nebo service. |
Rozlišení člověk vs stroj — pokud endpoint vyžaduje člověka, odmítni service token a naopak. |
email, name |
Jen u user tokenu. | UI: profil v navigaci. |
groups |
Array stringů (user: členství; service: service_groups). |
Authorization — if ('translate-editor' in groups) {...}. |
is_admin |
Bool, jen u user tokenu. | Super-admin přístup k admin endpointům. |
client_id, client_name |
Jen u service tokenu. | Auditní stopa: kdo (jaká služba) volá. |
4.4 Authorization v aplikaci
JWT ti říká kdo uživatel je. Co může dělat rozhoduješ ty — typicky podle groups:
function requireGroup(array $userGroups, string $required): void {
if (!in_array($required, $userGroups, true)) {
http_response_code(403);
exit(json_encode(['error' => 'forbidden']));
}
}
requireGroup($decoded->groups, 'translate-editor');
Skupiny si pojmenuj sám — typicky podle služby + role:
translate-editor,translate-admindeploy-deployer,deploy-viewermain-admin
Skupiny se zakládají v admin UI auth serveru (/admin/groups).
5. Volání /me (alternativa)
Pokud nechceš parsovat JWT v každé službě (např. prototyp, simpler-than-thou
microservice), můžeš volat /me:
curl https://auth.stromcom.cz/me \
-H "Authorization: Bearer <access_token>"
{
"token_use": "user",
"sub": "42",
"email": "ja@firma.cz",
"name": "Jan Novák",
"groups": ["translate-editor"],
"is_admin": false,
"client_id": "cli_xxxxxxxx",
"scopes": ["openid", "profile"]
}
Trade-off: volání /me per request přidá latenci a vytvoří závislost na dostupnosti
auth serveru. Production používej JWT verifikaci, /me jen pro debugging /
ad-hoc skripty.
6. Logout
GET https://auth.stromcom.cz/oauth/logout?post_logout_redirect_uri=https://moje-app.stromcom.cz/
Smaže auth session (cookie na auth.stromcom.cz). Tokeny v tvojí appce zůstávají platné
do své expirace — pokud chceš okamžitý logout, smaž si svoje cookies sám
(access_token, refresh_token).
Pro service tokeny logout neexistuje — token vyprší sám, nebo admin revokne klient v UI.
7. Klientské knihovny (návrhy)
Žádné nemáme jako interní package; doporučené hotové:
| Jazyk | Library | Tip |
|---|---|---|
| PHP | league/oauth2-client + custom Provider |
Pro auth code flow. Pro JWT verifikaci samostatně firebase/php-jwt. |
| PHP (Slim/Laravel) | Vlastní middleware + firebase/php-jwt |
Klient code je 100 řádků, není potřeba framework. |
| Node.js | openid-client (pro flow) + jose (verifikace) |
openid-client umí discovery přes /.well-known/openid-configuration. |
| Python | authlib |
Plný OIDC support. |
| Go | golang.org/x/oauth2 + github.com/golang-jwt/jwt/v5 |
Pro service account flow (M2M) si vystačíš s curl / built-in HTTP klientem v každém
jazyce — je to jedno POST.
8. Časté chyby a debugging
401 unauthorized z /me: Token expiroval (nech klienta refreshnout) nebo má špatný
podpis (zkontroluj kid v JWT vs kid v JWKS — možná rotace klíčů, refetchuj JWKS).
401 z /oauth/token: Špatný client_id/client_secret, nebo grant_type není
povolený pro tvého klienta (oauth_clients.grants v DB).
invalid_request u /oauth/authorize: Chybí state, redirect_uri neodpovídá
whitelistu (musí přesně sedět, vč. trailing slashe).
Tokeny "rotují", ale uživatel se odhlásí každých pár minut: Pravděpodobně neukládáš
nový refresh token z /oauth/token response — starý je revokovaný hned po výměně.
Service token nemá email claim: Správně, service tokeny mají client_id místo.
Pokud tvůj kód předpokládá email, větvi podle token_use=user|service.
Lokální dev volá auth.stromcom.cz a chce HTTPS: Použij localhost v redirect URI
přes http:// — auth server to akceptuje pro localhost. Pro stage/prod vždy HTTPS.
Cheat sheet
Web app (uživatelský login):
// 1. Nepřihlášený user → redirect na auth.stromcom.cz/oauth/authorize?...
// 2. Callback /oauth/callback → POST /oauth/token s code + verifier
// 3. Schovej JWT do httpOnly cookie
// 4. Per request: ověř JWT podpis přes JWKS (cache 1h)
// 5. Authorization: in_array('moje-skupina', $decoded->groups)
API klient / CI / cron (service token):
# Jednou ulož secret do env
export AUTH_CLIENT_ID=svc_xxxxxxxxxxxxxxxx
export AUTH_CLIENT_SECRET=...
# Pak v každém scriptu
TOKEN=$(curl -s -X POST https://auth.stromcom.cz/oauth/token \
-d grant_type=client_credentials \
-d client_id=$AUTH_CLIENT_ID \
-d client_secret=$AUTH_CLIENT_SECRET \
| jq -r .access_token)
curl https://moje-api.stromcom.cz/v1/things \
-H "Authorization: Bearer $TOKEN"
Endpointy:
| Endpoint | Pro koho |
|---|---|
GET /.well-known/openid-configuration |
Discovery — klientské knihovny si vytáhnou všechny URL. |
GET /.well-known/jwks.json |
Veřejný klíč pro JWT verifikaci. Cachuj 1h. |
GET /oauth/authorize |
Web app — start uživatelského loginu. |
POST /oauth/token |
Výměna code / refresh / client_credentials → JWT. |
GET /me |
Bearer token → user/service info. Pro debug. |
ANY /oauth/logout |
Logout auth session. |
Kontakt: admin auth serveru = support@stromcom.cz. Issues v repu auth.stromcom.cz.