Token do API - w bazie danych czy podpisany HMAC?

Zastanawiałem się ostatnio nad kwestią generowania tokenu do API. Wyobraźmy sobie API, które jest wykorzystywane poprzez klienta javascript. Po wysłaniu requestem loginu i hasła, dostajemy token, którego klient może później używać przy wykonywaniu kolejnych requestów.

Z reguły korzystam z jednej z 2 metod generowania takiego tokena:

  1. Wygenerowanie losowego tokena np. 64 znakowego i zapisanie go w bazie (często w redisie lub innym lekkim rozwiązaniu, chociaż tutaj jest taki problem, że redis nie wspiera domyślnie SSL).
  2. Wygenerowanie podpisanego tokena (coś jak signed request dla facebooka):
  • tworzę JSONa z odpowiednimi parametrami, np.: { "user_id": 10, "expires_at": 123456789 }
  • konwertuję to na base64
  • na podstawie takiej wartości i jakiegoś znanego tylko aplikacji klucza tworzę kod HMAC, który również traktuję base64
  • sklejam obie wartości, dzięki czemu dostaję {skonwertowany-json}.{kod-hmac}

Ogromnym plusem drugiego rozwiązania jest to, że nie muszę nigdzie takiego tokena zapisywać, a do walidacji wystarczy znać tylko klucz. Z drugiej strony cięższe jest unieważnienie takiego tokena - można to zrobić na różne sposoby, np. poprzez zapisywanie tylko unieważnionych tokenów albo stosowanie jakiegoś rodzaju “wersji” np. per użytkownik - zmiana wersji automatycznie unieważnia token., ale w niektórych przypadkach może to być nieco uciążliwe.

Jakie rozwiązanie najczęściej stosujecie u siebie? Ciekawy jestem opinii, bo sam z reguły nie umiem zdecydować, która z tych wersji jest lepsza (wiem, że to zależy też od konkretnego przypadku, ale powiedzmy, że mówimy o przypadku, gdzie mała jest szansa przewidzenia jak będzie wykorzystywane to API).

Ja uzywam raczej 2go rozwiazania, wlasnie z powodu braku koniecznosci utrzymywania ‘stanu’ (przez co np. autoryzacja akcji pomiedzy roznymi serwisami staje sie banalna). Z uniewaznianiem:

  • token byc wygenerowany na okreslony czas (czyli zawierac jakis epoch) po ktorym staje sie automatycznie ‘niewazny’; tak dzialaja tokeny w S3;
  • nr wersji per user - jakie to ma wady? Mozna by jako ‘wersje’ trzymac rowniez ostatni czas uniewaznienia tokenow (ale wtedy wszystkich) dla uzytkownika, wtedy kazdy jego token wygenerowany np. przed ta data stanie sie niewazny.

Z 2giej strony oAuth opiera sie na trzymaniu wszystkich tokenow w bazie (a nawet wiecej - rowniez przechowuje nonces czyli unikalne wartosci doczepiane do requestow po to, by zapobiec ‘replay attacks’), wiec moze to wyznacza ‘dobre standardy’ :slight_smile:

Ten problem przypomina mi ostatnią “dramę” z ciasteczkami. W zasadzie konsekwencje zastosowania rozwiązań, które opisałeś są bardzo podobne. Jak trzymamy w bazie, to możemy łatwo unieważnić, ale musimy mieć połączenie z bazą - takie rozwiązanie jest nieco wolniejsze i bardziej kłopotliwe. Jeśli używamy globalnego klucza, to trudniej unieważnić pojedynczy, ale nie musimy niczego trzymać w bazie - walidacja podpisu jest prosta i nie wymaga połączenia z bazą. Tak jak w przypadku ciastek - dobrze jest zawrzeć jakiś parametr typu “expires_at”.

Mark, piszesz o OAuth 1.0a? Z tego co kojarzę, to zamiast implementować te wszystkie “nonce”, itp. wystarczy udostępnić API tylko i wyłącznie po SSL - OAuth 2.0 jest dużo prostszy dzięki temu.

Pewnie o tym wiesz - ActiveSupport udostępnia wygodny interfejs do szyfrowania i podpisywania tego typu rzeczy (niekoniecznie jest to HMAC):

Dzięki, znałem encryptora, ale verifier mi jakoś umknął. Z tego co widzę, to używają HMAC do podpisania wiadomości: https://github.com/rails/rails/blob/0788d35a05bb3926500f7aa2930300144df779ac/activesupport/lib/active_support/message_verifier.rb#L65

Też mi się wydawało, że w Oauth 2.0 nie ma takich wymagań, ale będę musiał przejrzeć speca, już bardzo dawno do niego nie zaglądałem.

Kiedyś na blogu opisywałem jak wygląda szyfrowanie ciastek w nowych Railsach (pod spodem jest właśnie MessageEncryptor), może się komuś przydać: http://cowbell-labs.com/2013-04-10-decrypt-rails-4-session.html.

O co chodzi z “dramą z ciasteczkami”?

To chyba tutaj było opisane: http://seclists.org/fulldisclosure/2013/Sep/145

Z tego co pamiętam, to był wątek na HN, ale nie mogę znaleźć w tym momencie.

Tak, o 1.0a - jakos pod przeczytaniu tego wpisu: http://hueniverse.com/2012/07/oauth-2-0-and-the-road-to-hell/ uznalem, ze za wczesnie by w 2.0 sie pakowac.

Dzięki, nie widziałem tego. Chciałem się pozbyć CookieStore żeby mieć aplikacje w 100% wolne od ciastek (przynajmniej do momentu zalogowania), nareszcie mam powód mocniejszy niż samozadowolenie.

Możesz rozwinąć temat? Problem o którym była mowa w tym artykule występuje dopiero w momencie wylogowywania. Jak to się ma do Twojej aplikacji wolnej od ciastek?

Chciałbym żeby wejście na stronę nie powodowało otrzymania żadnego ciastka. Domyślny railsowy CookieStore od razu wysyła ciacho z jakimiś podstawowymi danymi sesji.