Stromcom Auth
Pro vývojáře Přihlásit se

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čítej sha256 + 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:

  1. Ověř state proti uloženému (CSRF).
  2. Vyměn code za 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-admin
  • deploy-deployer, deploy-viewer
  • main-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.