The Ultimate Guide to Angular User Login and Registration (Cookies, JWT)

V této příručce navrhneme a implementujeme kompletní řešení pro ověřování uživatelů včetně přihlášení uživatele, registrace a potvrzení účtu s rámcem Angular. Naučíme se strukturovat aplikaci se samostatným modulem zodpovědným za vizuální a logickou část autentizace uživatele. Navrhovaný přístup bude robustní a flexibilní, aby pokryl nejnáročnější požadavky v moderních webových aplikacích.

Kromě implementace frontendu našich případů použití porovnáme různé přístupy k provádění autentizace uživatelů používané v dnešním webu. Probereme různé scénáře nasazení aplikací a najdeme vhodný a nejbezpečnější přístup pro naše potřeby. Na konci tohoto tutoriálu budete mít jednoduchý, ale adaptabilní příklad přihlášení Angular, který můžete vyladit podle svých specifických potřeb. Kód bude napsán pro Angular 2+ a bude relevantní pro všechny novější verze (včetně Angular 11), ale diskutované koncepty platí také pro autentizaci AngularJS.

Struktura aplikace a návrh řešení

Abychom našli správné místo v aplikaci pro implementaci funkcí ověřování, musíme udělat krok zpět a zamyslet se nad architekturou aplikace Angular a modulárním designem. Naše aplikace bude rozdělena do modulů funkcí, z nichž každý se skládá z prezentační a logické části. Většina kódu, který budeme mít pro tento tutoriál, bude patřit do AuthModule . Tento modul bude obsahovat:

  • směrovatelné komponenty kontejneru pro přihlašovací, registrační a potvrzovací stránku,
  • dva chrániče routeru,
  • několik jemných služeb
  • konfiguraci směrování
  • zachycovač http

Dalším aspektem celé aplikace je směrování na nejvyšší úrovni. Chceme aplikaci rozdělit na autentizaci a aplikace díly. To zjednoduší strom tras a později nám umožní vytvořit dva odlišné strážce směrovačů pro použití správných zásad aktivace tras.

const routes: Routes = [
  { path: '', redirectTo: '/login', pathMatch: 'full' },
  {
    path: 'app',
    canActivate: [AppGuard],
    component: LayoutComponent,
    children: [
      { path: 'dashboard', component: DashboardComponent },
      { path: 'expenses', component: ExpensesComponent },
      { path: 'settings', component: SettingsComponent) }
    ]
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Než se pustíme do implementace, musíme si odpovědět na poslední velmi důležitou otázku. Protože protokol HTTP je bezstavový protokol žádost-odpověď, potřebujeme mít způsob, jak zachovat kontext uživatele po úspěšném přihlášení. V tomto článku popíšu dva nejpoužívanější přístupy:relace založené na souborech cookie a samostatné tokeny .

Relace založená na souborech cookie je založena na kontextu uživatele udržovaném na straně serveru . Každý kontext lze identifikovat pomocí identifikátoru relace, který je náhodně vygenerován pro každý prohlížeč a umístěn do souboru cookie. Když použijeme HttpOnly příznaku na tomto souboru cookie, bráníme našemu systému před útoky skriptování mezi weby, ale přesto musíme myslet na útoky na padělání požadavků mezi weby. Přístup založený na souborech cookie je velmi užitečný, když naše frontendová aplikace a backend API jsou hostovány na stejném zdroji (stejná doména a port). To je způsobeno základním pravidlem modelu zabezpečení webu, zásadou stejného původu, která nám nedovoluje sdílet stejné soubory cookie na více backendech. Jinými slovy, soubory cookie jsou určeny pro jednu doménu.

Druhý přístup může být užitečný, když je náš systém nasazen na různých zdrojích:frontendová aplikace je hostována na jiné doméně než backend API. V tomto případě by požadavky z frontendu na backend byly považovány za požadavky napříč původem a soubory cookie nastavené na zdroji backendu se nazývaly soubory cookie třetích stran . Soubor cookie třetí strany je stejný mechanismus, který používají analytické a sledovací systémy a lze jej snadno vypnout v moderních prohlížečích. Mnoho uživatelů se odhlásilo od souborů cookie třetích stran, protože se obávají o své soukromí na internetu. Někteří prodejci prohlížečů také vynakládají velké úsilí na úplné vymýcení souborů cookie třetích stran.

Co bychom tedy v takovém případě měli dělat? Můžeme použít jiný způsob poskytování kontextu uživatele mezi požadavky – HTTP Authorization Header. To vyžaduje programové čtení, ukládání a připojení autorizačního tokenu přenášeného prostřednictvím záhlaví (na rozdíl od souborů cookie). Abychom byli na stejné stránce, pamatujte, že session-id používané v cookies je také token, ale neprůhledný – nepřenáší žádné informace a je pouze klíčem k načtení relace na serveru. Jiný typ tokenu se nazývá samostatný token , do kterého můžeme vložit kontext uživatele. V roce 2015 Internet Engineering Task Force standardizoval JSON Web Token (JWT), který dokáže bezpečně přenášet informace mezi stranami. Díky kryptografickému podpisu můžeme předpokládat, že obsah JWT je autentický a integrální. Samostatná povaha JWT nám umožňuje načíst uživatelský kontext, jako jsou oprávnění a přihlašovací údaje, aniž bychom museli udržovat relaci na serveru (předpokládejme bezserverové a funkce jako služba). Můžeme se také integrovat se službami třetích stran bez omezení zásad stejného původu (například Firebase nebo AWS Amplify). Zde jsem popsal podrobnější vysvětlení webových tokenů JSON.

Domnívám se, že před implementací autentizace uživatele v aplikaci je velmi důležité porozumět základním rozdílům mezi těmito dvěma mechanismy. Můžete se také podívat na moje videa na YouTube, která zkoumají rozdíly mezi těmito dvěma přístupy a způsoby, jak lze JWT hacknout. Vybudujeme náš frontend schopný využívat jak soubory cookie relace, tak autentizaci pomocí tokenů pomocí webových tokenů JSON. Říkal jsem ti, že to bude flexibilní! 🤓

Podrobná implementace

Funkce přihlášení

Začněme částí uživatelského rozhraní – šablonou přihlašovací komponenty. Náš přístup k autentizaci uživatele je založen na dvojici e-mailu a hesla, takže v šabloně potřebujeme dvě vstupní položky. Všimněte si, že druhý vstup má atribut type="password" , který dává prohlížeči pokyn k vykreslení maskovaného vstupního prvku. Využíváme také hranatý materiál, abychom uživatelskému rozhraní poskytli pěkný vzhled a dojem. Níže naleznete příklad našeho přihlašovacího formuláře.

<form [formGroup]="loginForm">

  <div class="header">Login to your account</div>

  <mat-form-field>
    <input matInput type="email" id="email" placeholder="Email" autocomplete="off" formControlName="email" required>
  </mat-form-field>

  <mat-form-field>
    <input matInput type="password" id="password" placeholder="Password" autocomplete="off" formControlName="password" required>
  </mat-form-field>

  <div class="actions">
    <button mat-flat-button color="primary" type="submit" (click)="login()" [disabled]="!loginForm.valid">Login</button>
    <div class="separator">
      <span>OR</span>
    </div>
    <button mat-stroked-button type="button" routerLink="/signup">Sign up</button>
  </div>

</form>

Nyní je otázkou:jak převzít vstupní hodnoty od uživatele pro provedení přihlášení? Pro propojení HTML formuláře a vstupních prvků v pohledu s kódem komponenty můžeme využít některé direktivy z modulu Reactive Forms. Pomocí FormGroupDirective tímto způsobem [formGroup]="loginForm" , říkáme Angularu, že existuje vlastnost loginForm v komponentě, která by měla obsahovat instanci tohoto formuláře. Používáme FormBuilder vytvořit instance e-mailu a hesla FormControl . Ovládání e-mailu je také vybaveno vestavěným validátorem e-mailů.

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html'
})
export class LoginComponent implements OnInit {

  loginForm: FormGroup;

  constructor(private authService: AuthService,
    private formBuilder: FormBuilder,
    private router: Router) { }

  ngOnInit() {
    this.loginForm = this.formBuilder.group({
      email: ['', Validators.email],
      password: ['']
    });
  }

  get f() { return this.loginForm.controls; }

  login() {
    const loginRequest: LoginRequest = {
      email: this.f.email.value,
      password: this.f.password.value
    };

    this.authService.login(loginRequest)
      .subscribe((user) => this.router.navigate([this.authService.INITIAL_PATH]));
  }

}

Dalším krokem je provedení základních požadavků na provedení skutečného přihlášení po kliknutí na tlačítko. Protože chceme zpracovávat relace založené na souborech cookie i tokeny JWT, oddělujeme požadavky HTTP od zpracování logiky pomocí AuthStrategy rozhraní. V závislosti na zvoleném mechanismu skutečná implementace AuthStrategy je vstřikován v AuthService . To je možné díky konfiguračnímu nastavení, které určuje implementaci AuthStrategy se používá. Níže naleznete toto rozhraní se skutečnými implementacemi souborů cookie a JWT. Všimněte si, že authStrategyProvider tovární metoda se používá k registraci poskytovatele v AuthModule .

auth.strategy.ts

export interface AuthStrategy<T> {

  doLoginUser(data: T): void;

  doLogoutUser(): void;

  getCurrentUser(): Observable<User>;

}

export const AUTH_STRATEGY = new InjectionToken<AuthStrategy<any>>('AuthStrategy');

export const authStrategyProvider = {
  provide: AUTH_STRATEGY,
  deps: [HttpClient],
  useFactory: (http: HttpClient) => {
    switch (config.auth) {
        case 'session':
          return new SessionAuthStrategy(http);
        case 'token':
          return new JwtAuthStrategy();
      }
  }
};

session-auth.strategy.ts

export class SessionAuthStrategy implements AuthStrategy<User> {

  private loggedUser: User;

  constructor(private http: HttpClient) {}

  doLoginUser(user: User): void {
    this.loggedUser = user;
  }

  doLogoutUser(): void {
    this.loggedUser = undefined;
  }

  getCurrentUser(): Observable<User> {
    if (this.loggedUser) {
      return of(this.loggedUser);
    } else {
      return this.http.get<User>(`${config.authUrl}/user`)
        .pipe(tap(user => this.loggedUser = user));
    }
  }
}

jwt-auth.strategy.ts

export class JwtAuthStrategy implements AuthStrategy<Token> {

  private readonly JWT_TOKEN = 'JWT_TOKEN';

  doLoginUser(token: Token): void {
    localStorage.setItem(this.JWT_TOKEN, token.jwt);
  }

  doLogoutUser(): void {
    localStorage.removeItem(this.JWT_TOKEN);
  }

  getCurrentUser(): Observable<User> {
    const token = this.getToken();
    if (token) {
      const encodedPayload = token.split('.')[1];
      const payload = window.atob(encodedPayload);
      return of(JSON.parse(payload));
    } else {
      return of(undefined);
    }
  }

  getToken() {
    return localStorage.getItem(this.JWT_TOKEN);
  }
}

Jak můžete vidět výše při používání souborů cookie, nemusíme zpracovávat ID relace, protože je automaticky vkládáno do cookie prohlížečem. V případě autentizace pomocí tokenu JWT jej musíme někam uložit. Naše implementace jej vkládá do LocalStorage.

Nakonec, abychom spojili věci dohromady, AuthService volá doLoginMethod na AuthStrategy po provedení požadavku HTTP. Všimněte si, že konečné předplatné pozorovatelného streamu je připojeno v LoginComponent a zpracovává poslední krok k přesměrování na úvodní stránku po přihlášení.

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  public readonly LOGIN_PATH = '/login';
  public readonly CONFIRM_PATH = '/confirm';
  public readonly INITIAL_PATH = '/app/dashboard';

  constructor(
    private router: Router,
    private http: HttpClient,
    @Inject(AUTH_STRATEGY) private auth: AuthStrategy<any>
  ) { }

  signup(user: User): Observable<void> {
    return this.http.post<any>(`${config.authUrl}/signup`, user);
  }

  confirm(email: string, code: string): Observable<void> {
    return this.http.post<any>(`${config.authUrl}/confirm?`, {email, code});
  }

  login(loginRequest: LoginRequest): Observable<User> {
    return this.http.post<any>(`${config.authUrl}/login`, loginRequest)
      .pipe(tap(data => this.auth.doLoginUser(data)));
  }

  logout() {
    return this.http.get<any>(`${config.authUrl}/logout`)
      .pipe(tap(() => this.doLogoutUser()));
  }

  isLoggedIn$(): Observable<boolean> {
    return this.auth.getCurrentUser().pipe(
      map(user => !!user),
      catchError(() => of(false))
    );
  }

  getCurrentUser$(): Observable<User> {
    return this.auth.getCurrentUser();
  }

  private doLogoutUser() {
    this.auth.doLogoutUser();
  }

}

Přístup s AuthStrategy dělá AuthService implementace velmi flexibilní, ale pokud ji nepotřebujete, je zcela v pořádku obejít se bez ní. Obrázek níže ilustruje složení prezentovaných prvků.

Funkce registrace

Komponenta registrace je velmi podobná komponentě přihlášení. Máme podobný kód šablony s formulářem a vstupy. Hlavní rozdíl je v tom, co se stane po úspěšném požadavku HTTP. Zde se právě přesměrováváme na potvrzovací stránku z ConfirmComponent .

signup.component.html

<form [formGroup]="signupForm">

  <div class="header">Create your account</div>

  <mat-form-field>
    <input matInput type="email" id="signup_email" placeholder="Email" autocomplete="new-password" formControlName="email" required>
  </mat-form-field>

  <mat-form-field>
    <input matInput type="password" id="signup_password" placeholder="Password" autocomplete="new-password" formControlName="password" required>
  </mat-form-field>

  <div class="actions">
    <button mat-flat-button color="accent" type="submit" (click)="signup()" [disabled]="!signupForm.valid">Sign up</button>
    <div class="separator">
      <span>OR</span>
    </div>
    <button mat-stroked-button routerLink="/login">Login</button>
  </div>

</form>

signup.component.ts

@Component({
  selector: 'signup',
  templateUrl: './signup.component.html',
  styleUrls: ['./../auth.scss']
})
export class SignupComponent implements OnInit {

  signupForm: FormGroup;

  constructor(private authService: AuthService,
    private formBuilder: FormBuilder,
    private router: Router) { }

  ngOnInit() {
    this.signupForm = this.formBuilder.group({
      email: ['', Validators.email],
      password: ['']
    });
  }

  get f() { return this.signupForm.controls; }

  signup() {
    this.authService.signup(
      {
        email: this.f.email.value,
        password: this.f.password.value
      }
    ).subscribe(() => this.router.navigate([this.authService.CONFIRM_PATH]));
  }

}

Všimněte si také, že nepoužíváme AuthStrategy tady. Registrace je pouze odesláním nového páru přihlašovacího jména a hesla na backend a informování o nutnosti potvrzení účtu.

Funkce potvrzení účtu

Po úspěšném přihlášení je uživatel informován o e-mailu zaslaném na e-mailovou adresu. E-mail obsahuje speciální odkaz s potvrzovacím kódem. Tento odkaz ukazuje na stránku potvrzovací komponenty ve frontendové aplikaci. ConfirmComponent je navržen pro práci ve 2 režimech:před potvrzením a po úspěšném potvrzení. Podívejte se na níže uvedenou šablonu a všimněte si isConfirmed příznak v podmíněném příkazu.

confirm.component.html

<ng-container *ngIf="!isConfirmed; else confirmed">
  <div class="header">We've sent you a confirmation link via email!</div>
  <div>Please confirm your profile.</div>
</ng-container>

<ng-template #confirmed>
  <div class="header">Your profile is confirmed!</div>
  <button mat-flat-button color="primary" routerLink="/login">Login</button>
</ng-template>

To, co určuje zobrazený obsah komponenty, je booleovská hodnota nastavená v ngOnInit .

confirm.component.ts

@Component({
  selector: 'confirm',
  templateUrl: './confirm.component.html',
  styleUrls: ['./confirm.component.scss']
})
export class ConfirmComponent implements OnInit {

  isConfirmed = false;

  constructor(private activeRoute: ActivatedRoute, private authService: AuthService) { }

  ngOnInit(): void {
    const email = this.activeRoute.snapshot.queryParams.email;
    const code = this.activeRoute.snapshot.queryParams.code;

    if (email && code) {
      this.authService.confirm(email, code)
        .subscribe(() => this.isConfirmed = true);
    }
  }

}

Poslední chybějící část je pouze požadavek HTTP na zaslání páru e-mailu a odpovídajícího potvrzovacího kódu na backend v AuthService .

Auth.service.ts - potvrdit ()

  confirm(email: string, code: string): Observable<void> {
    return this.http.post<any>(`${config.authUrl}/confirm?`, {email, code});
  }

Po úspěšném potvrzení se na stránce zobrazí pobídka k přihlášení.

Objekt uživatele

Došli jsme do bodu, kdy je připraveno naše přihlášení a registrace s potvrzovacími funkcemi. Nyní musíme do našeho systému přidat nějaké chybějící kousky. Otázka zní:jak klient frontendu ví, kdo je přihlášen nebo jakou roli má daný uživatel? Způsob získání těchto informací se liší v závislosti na autentizačním mechanismu (na základě souborů cookie nebo tokenů). Protože již máme řádnou abstrakci těchto mechanismů, můžeme použít AuthStrategy rozhraní. Metoda getCurrentUser nám poskytne Observable objektu uživatele.

user.ts

import { Account } from './account';
import { Role } from './types';

export class User {
  id?: string;
  accountId?: string;
  account?: Account;
  email?: string;
  password?: string;
  role?: Role;
  confirmed?: boolean;
  tfa?: boolean;
}

Podívejte se na implementace v obou přístupech. V případě relací na straně serveru, pokud neexistuje žádná místní kopie přihlášeného uživatele, musíme požádat backend a uložit ji lokálně. V případě autentizace založené na tokenu JWT stačí rozbalit informace z tokenu. Protože chceme pouze užitečné zatížení, musíme řetězec rozdělit pomocí token.split('.')[1] a window.atob funkce dekóduje formát base64 tokenu.

session-auth.strategy.ts - getCurrentUser()

  getCurrentUser(): Observable<User> {
    if (this.loggedUser) {
      return of(this.loggedUser);
    } else {
      return this.http.get<User>(`${config.authUrl}/user`)
        .pipe(tap(user => this.loggedUser = user));
    }
  }

jwt-auth.strategy.ts - getCurrentUser()

  getCurrentUser(): Observable<User> {
    const token = this.getToken();
    if (token) {
      const encodedPayload = token.split('.')[1];
      const payload = window.atob(encodedPayload);
      return of(JSON.parse(payload));
    } else {
      return of(undefined);
    }
  }

  getToken() {
    return localStorage.getItem(this.JWT_TOKEN);
  }

Přizpůsobení uživatelského rozhraní

Vzhledem k tomu, že přihlášený uživatel může mít přiřazenu určitou konkrétní roli, musíme odpovídajícím způsobem přizpůsobit uživatelské rozhraní. Nejen, že konkrétní trasy jsou dostupné nebo nedostupné, ale některé prvky by se měly zobrazovat nebo ne. Můžeme se ručně zeptat na uživatelskou roli pokaždé, když potřebujeme vědět, zda má být prvek vykreslen pomocí ngIf , ale existuje chytřejší způsob. Navrhuji vytvořit vlastní strukturální direktivu, která potřebuje seznam rolí, pro které by se měl daný prvek zobrazovat. To by nám poskytlo elegantní způsob kompozice šablony. Podívejte se na příklad níže. Tlačítko se zobrazí pouze v případě, že aktuálně přihlášený uživatel má roli 'vlastník'.

  <div class="add">
    <button mat-fab color="primary" (click)="openExpenseDialog()" *forRoles="['owner']">+</button>
  </div>

To je možné díky forRoles implementace strukturální směrnice uvedené níže.

import { Directive, Input, ViewContainerRef, TemplateRef } from '@angular/core';
import { AuthService } from '../services/auth.service';

@Directive({
  selector: '[forRoles]'
})
export class ForRolesDirective {

  roles: string[];

  @Input()
  set forRoles(roles: string[]|string) {
    if (roles != null) {
      this.roles = Array.isArray(roles) ? roles : [roles];
      this.roles = this.roles.map(r => r.toUpperCase());
    } else {
      this.roles = [];
    }

    this.authService.getUserRole$().subscribe(
      role => {
        if (role && !this.roles.includes(role.toUpperCase())) {
          this.viewContainer.clear();
        } else {
          this.viewContainer.createEmbeddedView(this.templateRef);
        }
      }
    );
  }

  constructor(
    private viewContainer: ViewContainerRef,
    private templateRef: TemplateRef<any>,
    private authService: AuthService) { }

}

Pamatujte, že směrnice musí být deklarována v modulu Angular. V našem případě to deklarujeme v AuthModule a exportovat ji, aby byla dostupná vnějšímu světu.

Ochrana tras

Oprávnění a role uživatelů určují nejen viditelnost prvků uživatelského rozhraní. Na vyšší úrovni musíme omezit přístup k trasám aplikace. Díky našemu špičkovému směrování a oddělení na autentizaci a aplikaci je tento úkol velmi snadný. Potřebujeme Router Guards, který řídí přístup k těmto 2 částem.

@Injectable({
  providedIn: 'root'
})
export class AppGuard implements CanActivate {

  constructor(private authService: AuthService, private router: Router) { }

  canActivate(): Observable<boolean> {
    return this.authService.isLoggedIn$().pipe(
      tap(isLoggedIn => {
        if (!isLoggedIn) { this.router.navigate(['/login']); }
      })
    );
  }
}

Logika v AppGuard říká:POKUD uživatel není přihlášen, PAK se přesměrujte na přihlašovací stránku a nepovolte přístup do aplikační části.

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor(private authService: AuthService, private router: Router) { }

  canActivate(): Observable<boolean> {
    return this.authService.isLoggedIn$().pipe(
      tap(isLoggedIn => {
        if (isLoggedIn) {
          this.router.navigate([this.authService.INITIAL_PATH]);
        }
      }),
      map(isLoggedIn => !isLoggedIn)
    );
  }
}

Na druhou stranu instrukce v AuthGuard je právě naopak:POKUD je uživatel přihlášen PAK nepovolte zobrazení přihlašovací stránky a přesměrování na výchozí stránku. Viděli jsme, jak zaregistrovat AppGuard již v hlavním směrování. Nyní je dalším krokem registrace AuthGuard v AuthRoutingModule .

const routes: Routes = [
  {
    path: 'login', component: LoginComponent,
    canActivate: [AuthGuard]
  },
  {
    path: 'signup', component: SignupComponent,
    canActivate: [AuthGuard]
  },
  {
    path: 'confirm', component: ConfirmComponent,
    canActivate: [AuthGuard]
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class AuthRoutingModule { }

API požaduje ověření

Posledním prvkem v našem systému je autentizace odchozích požadavků. Při používání cookies nemusíme nic dělat – session-id je připojeno ke každému HTTP dotazu.

V případě JSON Web Token musíme mít vyhrazený kód pro přidání Authentication hlavička s tokenem k požadavkům. Nejšikovnější způsob je použít HttpInterceptor . Věnujte pozornost podmíněné kontrole režimu autentizace - token chtějte připojit pouze v případě, že je to nutné.

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private authService: AuthService, @Inject(AUTH_STRATEGY) private jwt: JwtAuthStrategy) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    if (config.auth === 'token' && this.jwt && this.jwt.getToken()) {
      request = this.addToken(request, this.jwt.getToken());
    }

    return next.handle(request).pipe(catchError(error => {
      if (error.status === 401) {
        this.authService.doLogoutAndRedirectToLogin();
      }
      return throwError(error);
    }));

  }

  private addToken(request: HttpRequest<any>, token: string) {
    return request.clone({
      setHeaders: { 'Authorization': `Bearer ${token}` }
    });
  }

}

Konečně, interceptor musí být registrován v providers seznam v AuthModule jak je uvedeno níže.

@NgModule({
  declarations: [ ... ],
  exports: [ ... ],
  imports: [ ... ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    },
    ...
  ]
})
export class AuthModule { }

Shrnutí a další kroky

I když máme kompletní a robustní řešení, existuje spousta vylepšení, která bychom mohli implementovat do vašeho systému, abychom zlepšili jeho zabezpečení.

Především dvoufaktorová autentizace (2FA) je v dnešní době stále aktuálnější. Útočníci používají různé strategie k získání neoprávněného přístupu k účtům, jako jsou útoky hrubou silou, slovníkové útoky, cpání pověření, únos relace a mnoho dalších. Jedním z nejjednodušších způsobů implementace 2FA je Google Authenticator, ale to je mimo rozsah tohoto článku. Dalším způsobem, jak zvýšit bezpečnost přihlašovacího systému, je omezit neúspěšné pokusy o přihlášení. To může být velmi složité na implementaci, protože pokud slepě zablokujeme přihlášení některého uživatele, útočníci by mohli pro konkrétní uživatele snadno provést Denial-of-Service (DoS) (například neustále používat nesprávné heslo automatizovaným způsobem). Existují chytrá řešení, jak tomu zabránit, jako jsou soubory cookie zařízení a důvěryhodní klienti.

A konečně, naše implementace nemá velmi důležitou funkci obnovení účtu (resetování hesla). Tato funkce může být popsána v budoucích výukových programech.

Je toto řešení bezpečné?

Ano i ne. Aby byly věci realistické, musíme si uvědomit, že ve webových aplikacích existuje spousta bezpečnostních rizik. Existují zranitelnosti, jako je padělání požadavků mezi weby při používání souborů cookie, skriptování mezi weby při ukládání tokenů do místního úložiště, nemluvě o tom, že implementace webových tokenů JSON na backendu je zásadní pro zabezpečení systému.

Chcete-li vytvořit zabezpečené webové systémy, musíte porozumět základům modelu zabezpečení webu, běžným bezpečnostním zranitelnostem a metodám prevence. Na frontendové straně aplikace je třeba se o mnoho postarat, ale nejdůležitější práce z hlediska bezpečnosti se provádí na backendu systému. Tomu se budeme věnovat v nadcházejících článcích.

Závěrečná slova

Naučili jsme se, jak přidat přihlašovací systém do aplikace Angular a vytvořit plně funkční přihlašovací a registrační stránku. Analyzovali jsme rozdíly mezi autentizací založenou na souborech cookie a bezstavovou autentizací pomocí webových tokenů JSON a poskytli jsme platné scénáře pro oba. Úplný zdrojový kód prezentovaných mechanismů najdete v mé aplikaci Budget Training na GitHubu.

Pokud se vám tento obsah líbí a chcete se dozvědět více, vřele doporučuji připojte se na seznam čekatelů na program WebSecurity Academy nebo WebSecurity Tuesday a každý týden obdržíte 2minutové krátké tipy.

Pokud máte nějaké dotazy, dejte mi vědět v komentářích. Děkuji za přečtení! 😎