Refresh Token JWT - kto inicjuje odświeżanie (user czy API)

0

Cześć,
piszę moje pierwsze API z autoryzacją etc, dlatego proszę o wyrozumiałość :)
Case:
User loguje się do aplikacji -> otrzymuje token i refresh token -> ważność tokenu to 15 minut -> po 15 minutach token wygasa.
Jak powinno zachować się API?

  1. Wysłać kod 401 i User musi się martwić o odświeżenie tokenu -> tj. wysłać kolejne żądanie o nowy token -> gdy go otrzyma wysłać kolejny raz żądanie o dane
  2. czy może lepiej aby w takiej sytuacji API domyślnie: zweryfikowało sygnaturę tokenu -> jeśli jest ok -> zweryfikować okres ważności tokenu -> jeśli minął -> zweryfikować refresh token -> jeśli jest ok -> wygenerować nowe tokeny -> zwrócić userowi dane o które prosi

W mojej ocenie drugie podejście jest właściwe, ale w necie większość rozwiązań chyba opiera się na pierwszym rozwiązaniu.
A jakie jest wasze zdanie? Jak Wy do tego podchodzicie?

Dodatkowo chciałem się zapytać jakie jest wasze podejście do unieważnionych Refresh Tokenów? Czy przechowujecie je przez okres ich użyteczności i przy każdym odnowianiu tokenów sprawdzacie czy używany refresh token nie został wcześniej usunięty czy może macie inne podejście? Bo jeśli ja mam przechowywać w bazie/pamięci unieważnione tokeny to chyba lepiej trzymać w pamięci tylko te aktywne tokeny i sprawdzać czy user próbuje wykorzystać z refresh tokena, który jest ciągle aktywny (będzie ich na pewno mniej niż tych nieaktywnych).

3

Jak powinno zachować się API?
1. Wysłać kod 401 i User musi się martwić o odświeżenie tokenu -> tj. wysłać kolejne żądanie o nowy token -> gdy go otrzyma wysłać kolejny raz żądanie o dane

Tym sposobem ciężko Ci będzie wiedzieć czy faktycznie czas na odświeżenie tokenu, czy może trzeba przekierować użytkownika do zalogowania na nowo, czy może faktycznie jest nieautoryzowany request, nawet jeśli token jest dobry.

  1. czy może lepiej aby w takiej sytuacji API domyślnie: zweryfikowało sygnaturę tokenu -> jeśli jest ok -> zweryfikować okres ważności tokenu -> jeśli minął -> zweryfikować refresh token -> jeśli jest ok -> wygenerować nowe tokeny -> zwrócić userowi dane o które prosi

Tym sposobem komplikujesz sytuację po stronie serwera, w dodatku jak mniemam- refresh token serwer będzie brał z requesta, a więc request będzie zawierał zarówno JWT token, jak i refresh token co nie wydaje się bezpieczne. W zasadzie takie podejście sprawia że nie ma sensu używać refresh token, bo równie dobrze możesz po prostu używać sam token JWT z ważnością taką jaką miałby refresh token.

Ja proponuję trzecie podejście, czyli:

  • Po zalogowaniu użytkownika klient otrzymuje JWT token, refresh token, oraz datę wygaśnięcia JWT tokena
  • Klient przechowuje lokalnie
  • Przed każdym requestem do API, klient sprawdza czy JWT token wygasnął lub wygaśnie niedługo (np. za minutę)
  • Jeśli JWT token wygasa, klient wysyła request o refresh, zamieszczając tam refresh token
    • Jeśli refresh token jest poprawy oraz nie wygasł, to serwer zwraca nowy JWT token, w przeciwnym razie serwer zwraca 401
    • Jeśli klient otrzymał 401 to wie że czas aby użytkownik zalogował się na nowo, i przekierowuje do logowania
  • Klient kontynuuje wysyłać normalny request
2
Aventus napisał(a):

W zasadzie takie podejście sprawia że nie ma sensu używać refresh token [...]

Trochę offtop, ale czy generalnie przy OAuth2 idea nie jest taka, że access tokeny lecą do Resource Servera, a refresh tokeny do Authorization Servera? W większości bieda implementacji z tutoriali niestety Resource Server i Authorization Server to jedno i to samo.

Wiem, że teraz JWT jest modne i pchane wszędzie, ale jak robimy API tylko dlatego, że na froncie mamy jakieś SPA, a nie dlatego, że z tego API rzeczywiście będą korzystać inne aplikacje poza naszym frontem to czy warto iść w JWT? Jaką to ma tak na prawdę zaletę nad zwykłym CookieAuthentication?

0

Faktycznie takie podejście ma większy sens :). Dziękuję.
A mogę jeszcze prosić o odpowiedź jak wy postępujecie z refresh tokenami, które wygasły? Czy je przechowujecie i przy każdym odświeżeniu sprawdzacie czy refresh token użytkownika nie został już wcześniej wykorzystany? Czy może porównujecie je z aktywnymi tokenami? A może jest również jakieś trzecie rozwiązanie?
Bardzo dziękuję za pomoc.

2

screenshot-20211107144455.png

0

@Azarien: @Wibowit Tak, tak, wiem... przepraszam za ten błąd - pisałem bardzo późno i nie zwróciłem na niego uwagi :( Postaram się pilnować następnym razem.

@some_ONE ogólnie to zgodzę się z Tobą. W moim przypadku JWT chyba jednak ma jakiś sens, bo u mnie dane będą pobierane z dwóch różnych niezależnych serwerów, więc muszę jakoś autoryzować userów na obu serwerach.

0

@some_ONE:

Trochę offtop, ale czy generalnie przy OAuth2 idea nie jest taka, że access tokeny lecą do Resource Servera, a refresh tokeny do Authorization Servera? W większości bieda implementacji z tutoriali niestety Resource Server i Authorization Server to jedno i to samo.

Albo nie znam tego, albo nie za bardzo rozumiem co masz na myśli. Czy poprzez "resource server" rozumiesz po prostu Web API do którego wysyłamy requesty związane z naszą aplikacją? Np. [POST] /invoices? Jeśli tak, to zależy całkowicie od implementacji i skali konkretnej aplikacji. Jeśli masz mała aplikację monolityczną, i tak korzystasz z tokenów JWT, to będziesz to wszystko miał w jednym miejscu. Nie orientuję się żeby OAuth2 miało tu cokolwiek do rzeczy. Mogę się oczywiście mylić, więc chętnie się dowiem więcej. Dla czego tak uważasz?

Wiem, że teraz JWT jest modne i pchane wszędzie, ale jak robimy API tylko dlatego, że na froncie mamy jakieś SPA, a nie dlatego, że z tego API rzeczywiście będą korzystać inne aplikacje poza naszym frontem to czy warto iść w JWT? Jaką to ma tak na prawdę zaletę nad zwykłym CookieAuthentication?

To tak naprawdę oddzielny temat od tego o co pyta OP. Oczywiście, często jest tak że cookie authentication wystarczy. JWT mają dodatkowe zalety takie jak to że problem CSRF nie występuje jeśli wysyłasz token w headerach a nie w cookies, mając token JWT po stronie klienta masz od razu dostęp do podstawowych informacji o użytkowniku bez konieczności odpytywania serwera itp.

0

@Aventus:

JWT mają dodatkowe zalety takie jak to że problem CSRF nie występuje jeśli wysyłasz token w headerach a nie w cookies

hmm, no ale jeżeli w headerze, to chyba musi(?) być wysłany z jsa, a jak z jsa, to XSSowalny jest token,

Generalnie zabezpieczyć się przed CSRF jest banalnie prosto, a przed XSSem ciężej bo jest wiele potencjalnych fuckupów.

Więc, gdy token będzie w HTTP Only Cookie, to ani XSS go chyba nie ruszy, a przed CSRFem się łatwo zabezpieczyć, chyba.

0

@WeiXiao: w którym miejscu ja wskazałem że JWT pomaga na problem XSS? :p

EDIT: Akurat XSS to oddzielna sprawa, i aplikacje często muszą się przed tym zabezpieczać. Korzystanie z auth cookies nic tu nie zmieni, bo usunęliśmy jedynie jedno potencjalne miejsce do ataku XSS.

1
Aventus napisał(a):

Albo nie znam tego, albo nie za bardzo rozumiem co masz na myśli. Czy poprzez "resource server" rozumiesz po prostu Web API do którego wysyłamy requesty związane z naszą aplikacją? Np. [POST] /invoices? Jeśli tak, to zależy całkowicie od implementacji i skali konkretnej aplikacji. Jeśli masz mała aplikację monolityczną, i tak korzystasz z tokenów JWT, to będziesz to wszystko miał w jednym miejscu. Nie orientuję się żeby OAuth2 miało tu cokolwiek do rzeczy. Mogę się oczywiście mylić, więc chętnie się dowiem więcej. Dla czego tak uważasz?

Tak, Resource Server to API zabezpieczone JWT, a Authorization Server to API/apka generująca te tokeny.
Swoją teorię bazowałem na doświadczeniach/poglądowych schematach OAuth i odpowiedziach na stacku :P Ale to chyba rzeczywiście trochę zbyt daleko idąca teoria.

W RFC dotyczącym OAutha pojawiają się takie stwierdzenia:
The authorization server may be the same server as the resource server or a separate entity.

Ale też:
Unlike access tokens, refresh tokens are intended for use only with authorization servers and are never sent to resource servers.

Schematy przepływów OAuth zazwyczaj pokazują Authorization Server jako oddzielny byt, ale wychodzi na to, że to szczegół implementacyjny.

  +--------+                                           +---------------+
  |        |--(A)------- Authorization Grant --------->|               |
  |        |                                           |               |
  |        |<-(B)----------- Access Token -------------|               |
  |        |               & Refresh Token             |               |
  |        |                                           |               |
  |        |                            +----------+   |               |
  |        |--(C)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(D)- Protected Resource --| Resource |   | Authorization |
  | Client |                            |  Server  |   |     Server    |
  |        |--(E)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(F)- Invalid Token Error -|          |   |               |
  |        |                            +----------+   |               |
  |        |                                           |               |
  |        |--(G)----------- Refresh Token ----------->|               |
  |        |                                           |               |
  |        |<-(H)----------- Access Token -------------|               |
  +--------+           & Optional Refresh Token        +---------------+

To tak naprawdę oddzielny temat od tego o co pyta OP. Oczywiście, często jest tak że cookie authentication wystarczy. JWT mają dodatkowe zalety takie jak to że problem CSRF nie występuje jeśli wysyłasz token w headerach a nie w cookies, mając token JWT po stronie klienta masz od razu dostęp do podstawowych informacji o użytkowniku bez konieczności odpytywania serwera itp.

Mają też dodatkowe wady :P

Jak nie trzymasz tokena w cookie tylko localStorage to tak jak wspomniał @WeiXiao tokeny podatne są na XSS. A jak trzymasz w httpOnly cookie (co niektórzy zalecają i chyba podobne zalecenia widziałem w raportach z audytu bezpieczeństwa apek) to i tak musisz się zabezpieczyć przed CSRF.

Inne wady używania JWT jako mechanizmu sesji na froncie:

  • brak możliwości revokowania tokenów, wiele apek ma możliwość zakończenia sesji z jednego urządzenia na wszystkich innych urządzeniach, jeśli mamy bezstanowe JWT to tego nie zrobimy, a przy stanowym JWT musimy trzymać stan albo blacklisty po stronie serwera, czyli w pewnym sensie wracamy do starej dobrej sesji :P
  • pisałeś, że mamy dostęp do podstawowych informacji o użytkowniku bez konieczności odpytywania serwera, tak to jest zaleta, ale też i wada, bo w takim razie w tokenie możemy mieć już nieaktualne dane, w zwykłych apkach webowych ja bym się raczej spodziewał, że jak przypiszę użytkownikowi rolę/zablokuję go to zmiana będzie obowiązywać od razu, a nie po np. 1 godzinie, albo przelogowaniu
  • implementacja "po swojemu" jest dużo trudniejsza (nie mówię o integracjach z Auth0, Azure AD B2C, czy Amazon Cognito), a większość tutoriali pokazuje jakieś bieda implementacje, które nie mają żadnych zalet nad zwykłą i prostą sesją

Na froncie chcemy mieć sesję użytkownika, to użyjmy do tego celu sesji, a nie jakiejś protezy z JWT.
JWT jest dobre do komunikacji pomiędzy serwerami.
Albo jak chcemy wystawić nasze API do użytku innych użytkowników, ale wtedy potrzebujemy pełnej implementacji OAuth2/OIDC, a nie tego czegoś co pokazują tutoriale, gdzie wystawiają RESTowy endpoint authenticate przyjmujący login i hasło w body i zwracający token. A takie publicznie wystawione API i tak będzie się różniło od API używanego przez front (SPA), więc na zwykłym froncie ja i tak bym używał sesji.

Warto sprostować coś, co poruszył @Botek w komentarzu, bo ja też wcześniej pisałem o uwierzytelnianiu przez "cookie", a chodziło mi o sesję (gdzie zazwyczaj używa się cookie do przenoszenia informacji o identyfikatorze sesji). Porównanie bardziej dotyczy sesje vs JWT.
Jemu chyba chodziło o to, że jako zaletę przy JWT podałeś ochronę przed CSRF, co nie jest zaletą JWT samą w sobie. Jak będę przesyłał identyfikator sesji w nagłówku, to też będę zabezpieczony przed CSRF.

0

@some_ONE:

W RFC dotyczącym OAutha pojawiają się takie stwierdzenia:
The authorization server may be the same server as the resource server or a separate entity.

Ale też:
Unlike access tokens, refresh tokens are intended for use only with authorization servers and are never sent to resource servers.

Wydaje mi się że tutaj chodzi o podkreślenie tego że refresh tokens nie mają nic wspólnego z uwierzytelnianiem requestów wysyłanych do resource endpoints, a nie o jakąś żelazną zasadę że zawsze należy to rozdzielić. Tym bardziej że Twój pierwszy cytat stwierdza jasno że resource i auth server to może być to samo.

Mają też dodatkowe wady :P

Oczywiście.

  • brak możliwości revokowania tokenów, wiele apek ma możliwość zakończenia sesji z jednego urządzenia na wszystkich innych urządzeniach, jeśli mamy bezstanowe JWT to tego nie zrobimy, a przy stanowym JWT musimy trzymać stan albo blacklisty po stronie serwera, czyli w pewnym sensie wracamy do starej dobrej sesji :P

To prawda, sam w jednej aplikacji taką sesję stosuję. Chociaż z pewną odwrotnością, bo jest to whitelist. Przy wylogowaniu użytkownika token jest z tej listy usuwany.

  • pisałeś, że mamy dostęp do podstawowych informacji o użytkowniku bez konieczności odpytywania serwera, tak to jest zaleta, ale też i wada, bo w takim razie w tokenie możemy mieć już nieaktualne dane, w zwykłych apkach webowych ja bym się raczej spodziewał, że jak przypiszę użytkownikowi rolę/zablokuję go to zmiana będzie obowiązywać od razu, a nie po np. 1 godzinie, albo przelogowaniu

A widziałeś że czasem w aplikacjach webowych trzeba się przelogować po dokonaniu jakiejś ważnej zmiany? :) Oczywiście, można to traktować jako wadę również.

  • implementacja "po swojemu" jest dużo trudniejsza (nie mówię o integracjach z Auth0, Azure AD B2C, czy Amazon Cognito), a większość tutoriali pokazuje jakieś bieda implementacje, które nie mają żadnych zalet nad zwykłą i prostą sesją

Nie zgadzam się z tym, w ASP Core to jest bardzo łatwe.

Na froncie chcemy mieć sesję użytkownika, to użyjmy do tego celu sesji, a nie jakiejś protezy z JWT.

No właśnie, tylko czasem nie chcesz mieć żadnej stanowej sesji a po prostu wyświetlić te informacje, które już masz dostępne. To samo dotyczy ról- możesz je wyciągnąć z tokena aby ograniczyć dostęp do pewnych elementów w UI. Oczywiście chodzi tu tylko o ograniczanie dostępu z perspektwy UX, w żadnym razie nie sugeruję tutaj że zabezpieczenia powinny być po stronie klienta. To nadal odpowiedzialność serwera!

Ogólnie to nie zrozum mnie źle- mi w ogóle nie zależy o udowadnianiu jakiejś wyższości JWT nad session cookies, lub na odwrót. Wszystko ma swoje zastosowania, jak i ma swoje wady i zalety.

0
Aventus napisał(a):

A widziałeś że czasem w aplikacjach webowych trzeba się przelogować po dokonaniu jakiejś ważnej zmiany? :) Oczywiście, można to traktować jako wadę również.

No niestety widziałem i nie uważam tego za coś dobrego :P

  • implementacja "po swojemu" jest dużo trudniejsza (nie mówię o integracjach z Auth0, Azure AD B2C, czy Amazon Cognito), a większość tutoriali pokazuje jakieś bieda implementacje, które nie mają żadnych zalet nad zwykłą i prostą sesją

Nie zgadzam się z tym, w ASP Core to jest bardzo łatwe.

Ale ja nie mówię o .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) tylko o sensownej obsłudze (wystawianie access/refresh tokenów) JWT, w sytuacji gdy nie integrujemy się z żadnym zewnętrznym dostawcą OAuth2/OIDC.

No właśnie, tylko czasem nie chcesz mieć żadnej stanowej sesji

Ale chcesz mieć sesję (bo czym innym jest zalogowanie się użytkownika, jak nie rozpoczęciem takiej sesji?), a żeby JWT się nadawały do sesji trzeba je protezować (chociażby trzymanie whitelisty/blacklisty, czy konieczność przelogowania użytkownika po zmianach).

a po prostu wyświetlić te informacje, które już masz dostępne. To samo dotyczy ról- możesz je wyciągnąć z tokena aby ograniczyć dostęp do pewnych elementów w UI. Oczywiście chodzi tu tylko o ograniczanie dostępu z perspektwy UX, w żadnym razie nie sugeruję tutaj że zabezpieczenia powinny być po stronie klienta. To nadal odpowiedzialność serwera!

Tylko dokładnie to samo mogę zrobić mając sesję i robiąc przy starcie prosty request /me, który praktycznie nic nie będzie kosztował.

Ogólnie to nie zrozum mnie źle- mi w ogóle nie zależy o udowadnianiu jakiejś wyższości JWT nad session cookies, lub na odwrót. Wszystko ma swoje zastosowania, jak i ma swoje wady i zalety.

No mi też, zresztą podałem przykłady, gdzie JWT ma sens.
Ja tylko twierdzę, że w przypadku apek webowych, które mają wystawione API tylko na potrzeby frontu, JWT nie daje nic, czego nie można osiagnąć zwykłą sesją.

Kilka linków na których bazuję swoje "wypociny"

1 użytkowników online, w tym zalogowanych: 0, gości: 1