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:

  1. 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.
  2. Nadajcie kluczowi przedrostek. Ciąg w rodzaju lix_live_... rozpoznają skanery sekretów w repozytoriach i w potokach logów.
  3. Osobny klucz na każdą integrację. Wtedy unieważnienie jednego nie kładzie pozostałych, a z rejestru widać, co dokładnie wyciekło.
  4. Nigdy nie wkładajcie klucza do ciągu zapytania. Adresy osiadają w logach serwera WWW, w nagłówku Referer i w historii przeglądarki. Wyłącznie nagłówek.
  5. 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.
  6. 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.