Klucze API i OAuth rozwiązują różne problemy, a wybór między nimi zaczyna się od jednego pytania: czy w schemacie jest strona trzecia?
Jeśli wasz serwer woła cudze API we własnym imieniu, potrzebujecie klucza. Jeśli cudza aplikacja woła wasze API w imieniu waszego użytkownika, potrzebujecie OAuth. Gdy strony trzeciej nie ma, OAuth dokłada ceremoniału, ale nie dokłada bezpieczeństwa.
Dalej: czym się naprawdę różnią, co zmieniło się w OAuth przez ostatnie lata i o jakiej właściwości kluczy API prawie nikt nie pisze, choć to ona rozstrzyga, jak będziecie z nimi żyć na produkcji.
Klucz API: jeden ciąg znaków mówiący „temu programowi wolno"
Klucz to długi, losowy ciąg, który wkładacie do nagłówka żądania:
X-Api-Key: lix_live_9f2c...
Serwer odnajduje klucz, ustala, do którego konta należy, i przepuszcza żądanie. Na tym cały mechanizm się kończy.
Wynikają z tego następujące właściwości:
- klucz sam nie wygasa. Żyje, dopóki ktoś go nie unieważni;
- klucz wskazuje program, a nie osobę. Kto z zespołu wykonał wywołanie, zwykle nie jest widoczne;
- klucz pozwala na wszystko, na co pozwala konto, o ile nie ograniczyliście uprawnień osobno;
- klucz jest jedynym sekretem. Wyciekł klucz — wyciekł dostęp, nie ma etapów pośrednich.
OAuth: protokół o zgodzie
OAuth 2.0 (RFC 6749) odpowiada na inne pytanie. Ma czterech uczestników: właściciela danych, aplikację, która ich chce, serwer autoryzacji oraz serwer przechowujący dane.
Przebieg wygląda mniej więcej tak. Aplikacja wysyła osobę na stronę samej usługi. Ta widzi, kto i o co prosi, i wyraża zgodę. Aplikacja dostaje kod, wymienia go na token dostępu i z nim woła API. Token żyje krótko — minuty albo godziny; po wygaśnięciu aplikacja wymienia token odświeżający na nowy.
Co to daje, czego klucz z założenia dać nie może:
- hasło użytkownika nigdy nie trafia do aplikacji. Cały sens tkwi właśnie tutaj;
- uprawnienia dzielą się na zakresy. „Czytanie linków" i „usuwanie linków" to osobne zgody;
- zgoda jest widoczna i odwoływalna. Użytkownik widzi listę podłączonych aplikacji i może odłączyć jedną, nie ruszając pozostałych;
- tokeny wygasają same. Skradziony token dostępu godzinę później jest bezwartościowy.
Ceną jest złożoność: serwer autoryzacji, rejestracja aplikacji, ekran zgody, przechowywanie i rotacja tokenów odświeżających oraz obsługa wygaśnięcia przy każdym wywołaniu.
Porównanie po osiach, które naprawdę decydują
| Klucz API | OAuth 2.0 | |
|---|---|---|
| Kogo poświadcza | program | użytkownika, w którego imieniu program działa |
| Czas życia | bezterminowo | token dostępu: minuty do godzin |
| Jak kończy się dostęp | unieważnić klucz | cofnąć zgodę albo token |
| Zakres uprawnień | zwykle całe konto | zakresy dla poszczególnych działań |
| Sekret w rękach strony trzeciej | sam klucz | wyłącznie tokeny, nigdy hasło |
| Nakład na wdrożenie | jeden nagłówek | serwer autoryzacji i cały cykl |
| Gdzie pasuje | serwer do serwera, skrypty, integracje wewnętrzne | aplikacje publiczne, marketplace'y, „Zaloguj przez…" |
Reguła wyboru
Pytanie nie brzmi, co jest bezpieczniejsze w oderwaniu od kontekstu, tylko kto komu przekazuje dostęp.
Wasz serwer → cudze API, konto wasze
→ klucz API
Cudza aplikacja → wasze API, konto waszego użytkownika
→ OAuth
Zadanie cykliczne, CI, integracja backendowa
→ klucz API
Użytkownik musi widzieć i cofać dostęp per aplikacja
→ OAuth
Odbieranie postbacków to podręcznikowy przypadek dla klucza. Kiedy wasz serwer melduje naszemu, że zamówienie zostało opłacone, w tej wymianie nie ma żadnego użytkownika, nie ma komu wyrazić zgody ani gdzie umieścić ekranu zgody. To samo dotyczy webhooków w drugą stronę.
Co zmieniło się w OAuth, kiedy nie patrzyliście
Połowa tekstów na ten temat opisuje OAuth z 2015 roku. Od tamtej pory sporo się zwęził.
W styczniu 2025 roku IETF opublikował RFC 9700, Best Current Practice for OAuth 2.0 Security. Dokument zbiera lata doświadczeń z prawdziwymi atakami i formalnie uznaje za przestarzałe dwa przepływy, które wcześniej uchodziły za dopuszczalne:
- implicit grant — ten, który zwracał token wprost w pasku adresu;
- resource owner password credentials — gdzie aplikacja pytała użytkownika wprost o login i hasło. Dokładnie to, czego OAuth miał się pozbyć.
Równocześnie PKCE stało się obowiązkowe dla wszystkich typów klientów, w tym serwerowych, a nie tylko dla mobilnych jak wcześniej.
Osobno warto wiedzieć: OAuth 2.1 wciąż pozostaje szkicem, a nie opublikowanym RFC. Zbiera te same zmiany w jednym dokumencie — obowiązkowe PKCE, dokładne porównywanie redirect URI, koniec z implicit i password grant, zakaz tokenów w ciągu zapytania. Powoływanie się na niego jako na obowiązujący standard jest przedwczesne.
Praktyczny wniosek: jeśli poradnik po OAuth proponuje przepływ implicit, poradnik się zestarzał.
Słaby punkt kluczy, o którym rzadko się mówi
Klucz nie wygasa. To czyni unieważnienie waszą jedyną dźwignią. I tu wychodzi rzecz niewygodna: unieważnienie zwykle nie działa natychmiast.
Sprawdzanie klucza przy każdym żądaniu oznacza sięganie do bazy danych. W API z realnym ruchem ten wynik trafia do pamięci podręcznej. U nas przypisanie klucza do konta leży w cache przez pięć minut. Między kliknięciem „unieważnij" a faktycznym odcięciem dostępu mija więc do pięciu minut, w trakcie których skompromitowany klucz nadal działa.
To nie przeoczenie, tylko wymiana: bez cache każde żądanie puka do bazy. Niemal każde API oparte na kluczach zawiera podobny układ, po prostu nie mówi się o tym głośno. Ma to znaczenie z dwóch powodów. Po pierwsze, przy prawdziwym wycieku unieważnienie klucza jest pierwszym działaniem, nie ostatnim — potem trzeba sprawdzić, co działo się w tych minutach. Po drugie, jest to dokładnie ten problem, który krótko żyjące tokeny OAuth atakują z drugiej strony: wygasają same, bez udziału bazy danych.
Jak żyć z kluczami i nie oberwać
Jeśli klucze wystarczają — a w większości integracji wystarczają — minimum wygląda tak:
- Przechowujcie wyłącznie skrót. Serwer porównuje skrót przesłanego klucza z zapisanym; oryginalny ciąg pokazuje się użytkownikowi raz, przy tworzeniu. Nasze sprawdzamy przez SHA-256.
- Nadajcie kluczowi przedrostek. Ciąg w rodzaju
lix_live_...rozpoznają skanery sekretów w repozytoriach i w potokach logów. - Osobny klucz na każdą integrację. Wtedy unieważnienie jednego nie kładzie pozostałych, a z rejestru widać, co dokładnie wyciekło.
- Nigdy nie wkładajcie klucza do ciągu zapytania. Adresy osiadają w logach serwera WWW, w nagłówku
Refereri w historii przeglądarki. Wyłącznie nagłówek. - Rotujcie według kalendarza, a nie po incydencie. Klucz, który raz wymieniono, wymienia się szybko. Ten, którego nie zmieniano nigdy, okaże się wpisany na sztywno w czterech miejscach.
- Patrzcie na datę ostatniego użycia. Klucz nietknięty od pół roku to nie zapas, tylko otwarte drzwi.
Czego używamy my
API Lix.li uwierzytelnia kluczem: nagłówek X-Api-Key, klucze tworzone i unieważniane w panelu, porównanie po skrócie, osobny limit żądań na każdy klucz. OAuth nie ma i jest to decyzja świadoma: zastosowania tego API leżą po stronie serwera — utworzyć link, pobrać statystyki, przyjąć konwersję — i w żadnym z nich nie występuje strona trzecia, której użytkownik musiałby udzielić zgody.
Gdyby pojawiło się zadanie wpuszczania cudzych aplikacji na konta naszych klientów, klucze przestałyby wystarczać: użytkownik musi widzieć, komu co pozwolił, i umieć odłączyć jedną aplikację bez psucia reszty. Dopiero tam OAuth przestaje być ceremoniałem i staje się jedyną uczciwą możliwością.
Same wywołania opisuje dokumentacja API, łącznie z kodami odpowiedzi dla klucza nieprawidłowego i dla unieważnionego. Dwa sąsiednie wątki warte osobnego zgłębienia to webhooki, jeśli wolicie zdarzenia odbierać zamiast o nie odpytywać, oraz różnica między przekierowaniami 301, 302, 307 i 308 — bo 301 zamienia POST na GET, co dotyka wprost odbierania postbacków.