Magic Link autentizace
Magic Link poskytuje autentizaci bez hesla prostřednictvím e-mailu. Uživatelé kliknou na bezpečný, časově omezený odkaz a přihlásí se bez zadávání hesla.
Jak to funguje
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Uživatel │ │ Vaše App │ │ Klubero │ │ E-mail │
│ │ │ │ │ SSO │ │ Server │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
│ 1. Zadá email │ │ │
│ ──────────────────►│ │ │
│ │ │ │
│ │ 2. Požádá o magic │ │
│ │ link │ │
│ │ ──────────────────►│ │
│ │ │ │
│ │ │ 3. Odešle email │
│ │ │ ──────────────────►│
│ │ │ │
│ 4. Obdrží email │ │ │
│ ◄───────────────────────────────────────────────────────────│
│ │ │ │
│ 5. Klikne na link │ │ │
│ ────────────────────────────────────────► │
│ │ │ │
│ 6. Přihlášen, redirect do aplikace │ │
│ ◄───────────────────────────────────────│ │
Krok 1: Požádání o Magic Link
Endpoint: POST /api/magiclink/send (anonymní)
curl -X POST https://your-sso-domain.com/api/magiclink/send \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"clientId": "my-app",
"redirectUri": "https://myapp.com/callback",
"responseType": "code",
"scopes": "openid profile email",
"state": "xyz",
"codeChallenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
"codeChallengeMethod": "S256"
}'
Tělo požadavku (CreateMagicLinkRequest, camelCase):
| Pole | Povinné | Popis |
|---|---|---|
email | Ano | E-mailová adresa uživatele |
clientId | Ne | Client ID registrované aplikace (validováno oproti registraci) |
redirectUri | Ne | Redirect URI (musí odpovídat registrovanému u klienta) |
scopes | Ne | Požadované scopes (oddělené mezerou) |
state | Ne | OAuth parametr state |
responseType | Ne | OAuth parametr response_type (např. code) |
responseMode | Ne | OAuth parametr response_mode |
nonce | Ne | OAuth parametr nonce (OIDC) |
codeChallenge | Ne | PKCE code challenge |
codeChallengeMethod | Ne | PKCE metoda (S256 nebo plain) |
OAuth parametry se předávají jako samostatná pole (nikoli jako jeden returnUrl). Pokud jsou vyplněny clientId a redirectUri, jsou validovány oproti registrovanému klientovi a musí odpovídat.
Odpověď (MagicLinkResultModel):
{
"success": true,
"errorMessage": null,
"emailSent": true,
"expiresAt": null
}
Pro platného klienta je odpověď vždy úspěšná (success: true), aby se zabránilo útokům typu email enumeration. Uživatel obdrží e-mail pouze pokud jeho účet existuje.
Pokud je zadán neplatný clientId nebo redirectUri, který neodpovídá registraci, vrátí endpoint HTTP 400 s ValidationProblemDetails, resp. tělem { "success": false, "errorMessage": "..." }.
Krok 2: Uživatel klikne na odkaz
E-mail obsahuje odkaz v tomto formátu:
https://your-sso-domain.com/Account/MagicLink?token=BASE64URL_TOKEN&...
Odkaz obsahuje token a "ticket" nesoucí předané OAuth parametry (client, redirect URI, scopes, state, PKCE apod.), aby po přihlášení mohlo pokračovat autorizační flow.
Krok 3: Dokončení autentizace
Když uživatel klikne na odkaz:
- Token je validován (existuje, nevypršel, nebyl použit)
- Uživatel je přihlášen pomocí cookie
- Pokud jsou přítomny OAuth parametry: Redirect na
/connect/authorize(pokračuje normální flow) - Pokud nejsou OAuth parametry: Redirect na uživatelský portál
Charakteristiky tokenu
| Vlastnost | Hodnota |
|---|---|
| Formát | 256-bitová náhodná hodnota, URL-safe Base64 kódování |
| Životnost | 15 minut |
| Použití | Pouze jednorázové (spotřebován při kliknutí) |
| Uložení | SHA256 hash uložen v databázi |
Validace tokenu (bez spotřebování)
Endpoint: GET /api/magiclink/validate?token= (anonymní)
Ověření platnosti tokenu bez jeho spotřebování:
curl "https://your-sso-domain.com/api/magiclink/validate?token=TOKEN_VALUE"
Odpověď (ValidateMagicLinkResult) — platný token:
{
"valid": true,
"userGuid": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"email": "j***@example.com",
"errorMessage": null,
"errorCode": null
}
Odpověď — neplatný token:
{
"valid": false,
"userGuid": null,
"email": null,
"errorMessage": "Token expired.",
"errorCode": 2
}
Pole errorCode je číslo podle tabulky v sekci Chybové kódy. Odpověď neobsahuje pole expiresAt.
Zrušení všech čekajících odkazů
Endpoint: POST /api/magiclink/revoke-all (vyžaduje autentizaci)
Zrušení všech čekajících magic linků pro autentizovaného uživatele:
curl -X POST https://your-sso-domain.com/api/magiclink/revoke-all \
-H "Authorization: Bearer ACCESS_TOKEN"
Odpověď:
{
"revokedCount": 2
}
Chybové kódy
Pole errorCode v odpovědi validate je číselná hodnota (výčet se serializuje jako číslo):
errorCode | Význam | Popis | Akce uživatele |
|---|---|---|---|
1 | TokenNotFound | Token neexistuje | Požádejte o nový magic link |
2 | TokenExpired | Token vypršel (>15 minut) | Požádejte o nový magic link |
3 | TokenAlreadyUsed | Token byl již použit | Požádejte o nový magic link |
4 | UserNotFound | Uživatelský účet nenalezen | Kontaktujte podporu |
5 | UserNotActive | Uživatelský účet deaktivován | Kontaktujte podporu |
6 | UserLocked | Účet uzamčen (příliš mnoho neúspěšných pokusů) | Počkejte nebo kontaktujte podporu |
7 | InvalidTokenFormat | Token je poškozený | Požádejte o nový magic link |
8 | InvalidTicket | Neplatný ticket s OAuth parametry | Požádejte o nový magic link |
Integrace s OAuth flow
Pro integraci magic linku s vaším OAuth flow:
- Připravte si stejné OAuth parametry jako u běžného flow (
clientId,redirectUri,scopes,state, případně PKCE) - Předejte je jako samostatná pole v požadavku na
/api/magiclink/send - Po kliknutí na odkaz uživatel automaticky pokračuje v OAuth flow (SSO z ticketu obnoví autorizační požadavek)
Příklad:
curl -X POST https://your-sso-domain.com/api/magiclink/send \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"clientId": "my-app",
"redirectUri": "https://myapp.com/callback",
"responseType": "code",
"scopes": "openid profile email",
"state": "xyz",
"codeChallenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
"codeChallengeMethod": "S256"
}'