[SPA/Security] Front-end autentykacja JTW i co dalej z tokenem?

0

Hej. siedzę sobie nad próbną aplikacją webową. Front-end w Angularze 10, back-end w ASP.NET Core 2.2. Mam małą zagwozdkę. Do końca nie rozumiem co powinno się stać dalej z tokenem autentykacji?

Dla przykładu, w aplikacji klienta wołam API które mi zwraca:

JwtSecurityToken token = new JwtSecurityToken(
                    issuer: _configuration["Auth:JsonWebToken:Issuer"],
                    audience: _configuration["Auth:JsonWebToken:Audience"], claims: claims, notBefore: now,
                    expires: now.Add(TimeSpan.FromMinutes(tokenExpirationMins)), signingCredentials: new SigningCredentials( issuerSigningKey, SecurityAlgorithms.HmacSha256));
                string encodedToken = new JwtSecurityTokenHandler().WriteToken(token);

                // build & return the response
                TokenResponseViewModel response = new TokenResponseViewModel()
                {
                    Token = encodedToken,
                    Expiration = tokenExpirationMins,
                    Email = user.Email,
                    User = user.UserName
                };
                return Json(response);

I tu jest wszystko jasne, wszystko jest ok. W dalszej części mogę się odpytywać auth.service.ts o to czy użytkownik jest zalogowany, który użytkownik jest zalogowany, mogę też się dowiedzieć jaki jest token zalogowanego użytkownika:

 // Retrieves the auth JSON object (or NULL if none)
  getAuth(): TokenResponse | null {
    if (isPlatformBrowser(this.platformId)) {
      var i = localStorage.getItem(this.authKey);
      if (i) {
        return JSON.parse(i);
      }
    }
    return null;
  }
  // Returns TRUE if the user is logged in, FALSE otherwise.
  isLoggedIn(): boolean {
    if (isPlatformBrowser(this.platformId)) {
      return localStorage.getItem(this.authKey) != null;
    }
    return false;
  }

Gdzie token.response.ts model wygląda tak:

export interface TokenResponse {
  token: string,
  expiration: number,
  email: string,
  user: string
}

Do końca nie jestem pewien jak mogę wykorzystać token: string?? Jak mogę dalej go wykorzystać komunikując się z API?

2

Uderzając z kolejnymi zapytaniami do API powinieneś przekazywać ten token w header. Na tej podstawie możesz identyfikować użytkowników/zabezpieczać endpointy przed nieautoryzowanym dostępem.

3

Do każdego requesta do zabezpieczonego endpointa musisz w header dodawać tego tokena, a dokładniej access token. Jeżeli to JWT, to po zalogowaniu powinieneś otrzymać refresh i access token. JWT nie jest zapisywany do bazy.
PS: Jeżeli chodzi o identyfikację użytkownika, to nie wiem jak w .NET, ale w Django na podstawie tokena Od razu można wyciągnąć użytkownika z requesta.

5

Doczytaj doczytaj, hasła takie jak właśnie JWT albo OAuth2.

JWT ogólnie jest bezstanowy. Także, co ciekawe, nawet jak "wylogujesz" usera z aplikacji i nie będzie mieć sesji, to jeśli ma jeszcze JWT i jeszcze ten token nie wygasł, to nadal będzie mógł uderzać do zabezpieczonego API.

Druga ciekawa sprawa, to JWT ma swój czas trwania, i nalicza się on od czasu jego nadania. Czyli jeśli np user jest "na końcówce" jego życia, wykona http request do backendu i się powiedzie, za sekundę może już dostac 401 mimo, że user cały czas był aktywny.

Później znowu zaczyna się zabawa z refreshowaniem - ten "normalny" access token ma zazwyczaj krótki czas życia (kilkanaście / kilkadziesiąt minut), a refresh token kilka h - później, dzięki refresh tokenowi, dostaje się nowy access token.

Jest też kwestia trzymania JWT po stronie frontendu - zazwyczaj wybiera się pomiędzy localStorage oraz cookie (zalecane podejście - jeśli jest httpOnly i secure)

JWT ogólnie jest pomocne przy komunikacji service-to-service, czyli jak z backendu robisz np rest calla do innego backendu - dodajesz JWT i elo

0
Pinek napisał(a):

jak "wylogujesz" usera z aplikacji i nie będzie mieć sesji

Masz na myśli wylogowanie usera na serwerze?

Pinek napisał(a):

za sekundę może już dostac 401 mimo, że user cały czas był aktywny.

To jest do przełknięcia. Właśnie szukałem rozwiązania na wypadek kiedy token jest expired, ale wciąż przechowywany. To rozwiązanie wydaje mi się być najodpowiedniejszym. Ciekaw jestem co inni sądzą o tym?

Pinek napisał(a):

Później znowu zaczyna się zabawa z refreshowaniem

To jest kolejna sprawa na mojej liście do zaimplementowania. Na szczęście znalazłem przykład w jednym z podręczników, może uda mi się też coś w necie wygrzebać konkretnego.

Pinek napisał(a):

Jest też kwestia trzymania JWT po stronie frontendu - zazwyczaj wybiera się pomiędzy localStorage oraz cookie

Spotkałęm się już z wykorzystaniem localStorage. A co jeśli mam zamiar odwoływać się do auth.service.ts w ten sposób, jeśli tylko będę potrzebował tokena: const token = this.auth.getAuth().token;? Przy czym

getAuth(): TokenResponse | null {
    if (isPlatformBrowser(this.platformId)) {
      var i = localStorage.getItem(this.authKey);
      if (i) {
        return JSON.parse(i);
      }
    }
    return null;
  }
2
bakunet napisał(a):

Masz na myśli wylogowanie usera na serwerze?

Tak

Właśnie szukałem rozwiązania na wypadek kiedy token jest expired, ale wciąż przechowywany. To rozwiązanie wydaje mi się być najodpowiedniejszym. Ciekaw jestem co inni sądzą o tym?

Nom, spoko

A co jeśli mam zamiar odwoływać się do auth.service.ts

Kodu frontowego ja nie znaju xd

2

Spotkałęm się już z wykorzystaniem localStorage. A co jeśli mam zamiar odwoływać się do auth.service.ts w ten sposób, jeśli tylko będę potrzebował tokena: const token = this.auth.getAuth().token;? Przy czym

getAuth(): TokenResponse | null {
    if (isPlatformBrowser(this.platformId)) {
      var i = localStorage.getItem(this.authKey);
      if (i) {
        return JSON.parse(i);
      }
    }
    return null;
  }

getAuth() zwraca obiekt, więc w miarę potrzeb możesz z niego wziąć access, refresh, albo wyrzucić to do osobnej metody, która zwraca gotowy header do zapytania

0

Odgrzewając temat, wiem, że OAuth jest wykorzystane przy logowaniu przez FB bądź Twitter. Też ASP.NET Core 3.x wspiera mocno OAuth. Angular ma wygodne rozwiązania dla żądań, chcę jeszcze ogarnąć jak to działa po stronie serwera, bo do końca tego nie rozumiem.

Pinek napisał(a):

Doczytaj doczytaj, hasła takie jak właśnie JWT albo OAuth2.

I ciekaw jestem, z tych dwóch rozwiązań, które polecisz?

1

OAuth2 nie wyklucza JWT. Format access tokenu nie jest jakieś ściśle zdefiniowany, więc równie dobrze może być to JWT. Widziałem już systemy, które tak robiły.

2

Dokładnie - OAuth(2) to standard, a JWT to format danego tokena. Najczęściej właśnie używa się JWT (m.in. w standardzie OAuth2)

0
Pinek napisał(a):

Dokładnie - OAuth(2) to standard, a JWT to format danego tokena. Najczęściej właśnie używa się JWT (m.in. w standardzie OAuth2)

OAuth, jak dobrze rozumiem, to po prostu wystawienie API logowania, w tym standardzie, dla klienta? Trochę mnie wkurza OAuth bo w przykłądach które przerabiałem to sporo się dzieje w bibliotekach, zarówno po stronie klietna, jak i serwera, i nie do końca kumam co się dzieje "pod maską".

2
bakunet napisał(a):

OAuth, jak dobrze rozumiem, to po prostu wystawienie API logowania

Nie do końca po prostu API. To jest standard co do flow pomiędzy (najczęściej) różnymi serwerami. Popatrz na grafiki, np taka:

taka

2
bakunet napisał(a):
Pinek napisał(a):

Dokładnie - OAuth(2) to standard, a JWT to format danego tokena. Najczęściej właśnie używa się JWT (m.in. w standardzie OAuth2)

OAuth, jak dobrze rozumiem, to po prostu wystawienie API logowania, w tym standardzie, dla klienta? Trochę mnie wkurza OAuth bo w przykłądach które przerabiałem to sporo się dzieje w bibliotekach, zarówno po stronie klietna, jak i serwera, i nie do końca kumam co się dzieje "pod maską".

Przejdź cały flow używając curla jak prawdziwy facet, a nie jakieś biblioteki :)

0
tsz napisał(a):

Przejdź cały flow używając curla jak prawdziwy facet, a nie jakieś biblioteki :)

Się śmiej, ale na przykład tutaj, idę sobie od metody do metody, aż trafię na coś takiego:

loginWithPassword(userName: string, password: string) {
        const header = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' });
        const params = new HttpParams()
            .append('username', userName)
            .append('password', password)
            .append('client_id', this.clientId)
            .append('grant_type', 'password')
            .append('scope', this.scope);

        this.oauthService.issuer = this.baseUrl;

        return from(this.oauthService.loadDiscoveryDocument())
            .pipe(mergeMap(() => {
                return this.http.post<LoginResponse>(this.oauthService.tokenEndpoint, params, { headers: header });
            }));
    }

gdzie libka:

import { OAuthService } from 'angular-oauth2-oidc';
private oauthService: OAuhService,

I teraz nie wiem:

  • jaki endpoint jest wołany w Web API?
  • przez co nie mogę debugować kolejnej(!!) biblioteki w Web API
  • co dostaje Web API wiem, ale nie wiem co zwraca i jak.

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