<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

🔐 Segurança do roteador Angular: Guardas e interceptadores em ação

Introdução: Segurança é um requisito, não uma opção. Aplicativos Angular modernos lidam com dados confidenciais: perfis de usuário, painéis de empresa, painéis de administração. Deixar essas seções desprotegidas expõe seu aplicativo a acesso não autorizado, roubo de token e violações de privacidade. Angular fornece um sistema de segurança de roteamento integrado e combinável: Route Guards e HTTP Interceptors. Este guia mostra como construir um sistema de segurança em camadas usando ferramentas modernas Angular 17+. Veremos cada mecanismo detalhadamente, com exemplos de código reais prontos para uso em produção. Tópicos: #Angular #Guards #Interceptor #WebDevelopment #FrontendDevelopment Arquitetura de segurança do frontend Antes de entrar no código, é essencial entender como a segurança é colocada em camadas em uma aplicação Angular: Nível 1 – Route Guards: eles controlam se o usuário pode navegar para uma determinada rota antes mesmo de o componente ser criado. Nível 2 – Interceptores HTTP: Adicione tokens de autenticação a cada solicitação de saída e lide com respostas de erro (401, 403) globalmente. Nível 3 – Validação Backend: o backend deve sempre validar a autenticação e autorização. A segurança do frontend é uma defesa adicional, não a única. Seção 1: CanActivate — Protegendo Rotas CanActivate é a proteção mais comum. Ele é executado antes que o Angular ative uma rota e seu componente associado. Se retornar false ou UrlTree , a navegação é bloqueada e o usuário é redirecionado. Do Angular 15 em diante, a abordagem recomendada é a função guard ( CanActivateFn ) com inject() em vez de classes com interfaces obsoletas. Isso elimina clichês e aproveita ao máximo a injeção de dependência funcional. Guarda de autenticação básica // core/guards/auth.guard.

ts importar {injetar} de '@angular/core'; importar { CanActivateFn, Router, ActivatedRouteSnapshot, RouterStateSnapshot } de '@angular/router'; importar { AuthService } de '../services/auth.service'; exportar const authGuard: CanActivateFn = (rota: ActivatedRouteSnapshot, estado: RouterStateSnapshot,) => { const auth = inject (AuthService); const roteador = injetar(Roteador); if (auth.isAuthenticated()) { return true; } // Preserva a URL solicitada para redirecionamento após o login return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url }, }); }; Guard para a função administrativa // core/guards/admin.guard.ts import { inject } from '@angular/core'; importar {CanActivateFn, Router} de '@angular/router'; importar { AuthService } de '../services/auth.service'; export const adminGuard: CanActivateFn = (rota, estado) => { const auth = inject(AuthService); const roteador = injetar(Roteador); if (!auth.isAuthenticated()) { return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } }); } if (!auth.hasRole('admin')) { return router.createUrlTree(['/403']); } retornar verdadeiro; }; AuthService com JWT e Sinais // core/services/auth.service.ts import { Injetável, sinal, computado } de '@angular/core'; importar { HttpClient } de '@angular/common/http'; importar {Roteador} de '@angular/router'; importar {tap} de 'rxjs/operadores'; interface TokenPayload {sub:string; e-mail: string; função: 'administrador' | 'usuário'; iat: número; exp: número; } função decodeJwt(token: string): TokenPayload | null { tentar { const base64Payload = token.split('.')[1]; const decodificado = atob(base64Payload.replace(/-/g, '+').replace(/_/g, '/')); retornar JSON.parse (decodificado) como TokenPayload; } pegar {retornar nulo; } } @Injectable({ provideIn: 'root' }) export class AuthService { private static readonly TOKEN_KEY = 'portfolio_token'; private readonly _payload = sinal(this.readStoredPayload()); somente leitura currentUser = this._payload.

asReadonly(); readonly isAdmin = computado(() => this._payload()?.role === 'admin'); construtor (http privado: HttpClient, roteador privado: roteador) {} login (email: string, senha: string) { return this.http .post<{ access_token: string }>('/api/auth/login', { email, senha }) .pipe(tap(({ access_token }) => { localStorage.setItem(AuthService.TOKEN_KEY, access_token); this._payload.set(decodeJwt(access_token)); this.router.navigate(['/login'] } getToken(): string | null { return localStorage.getItem(AuthService.TOKEN_KEY }); isAuthenticated(): boolean { const token = this.getToken(); if (!token) return false; TokenPayload | null { const token = localStorage.getItem(AuthService.TOKEN_KEY); o Angular baixa o pacote JavaScript do formulário apenas quando o usuário navega para essa rota. alterando a URL. CanMatch resolve este problema: se retornar false , Angular não avalia a rota e nunca baixa o pacote associado. // core/guards/can-match-admin.guard.ts import { inject } from '@angular/core'; CanMatchFn = () => { const auth = inject(AuthService); const router = inject(Router); if (auth.isAuthenticated() && auth.hasRole('admin')) return true;

ts import {Rotas} de '@angular/router'; importar {authGuard} de './core/guards/auth.guard'; importar {adminGuard} de './core/guards/admin.guard'; importar {canMatchAdmin} de './core/guards/can-match-admin.guard'; export const APP_ROUTES: Rotas = [ { caminho: 'admin', loadChildren: () => import('./features/admin/admin.routes').then(m => m.ADMIN_ROUTES), canActivate: [adminGuard], canMatch: [canMatchAdmin], }, { caminho: 'dashboard', loadComponent: () => import('./features/dashboard/dashboard.component').then(c => c.DashboardComponent), canActivate: [authGuard], }, { caminho: 'login', loadComponent: () => import('./features/auth/login.component').then(c => c.LoginComponent) }, { caminho: '', redireccionamentoTo: 'dashboard', pathMatch: 'full' }, { caminho: '**', redireccionamentoTo: 'login' }, ]; Seção 3: CanDeactivate — Protegendo formulários não salvos Um cenário comum: o usuário está preenchendo um formulário longo e acidentalmente clica em um link de navegação. Sem CanDeactivate , todas as alterações são perdidas silenciosamente. Com esse visual, aparece um pedido de confirmação. Guard CanDeactivate Genérico e Reutilizável // core/guards/dirty-form.guard.ts import { CanDeactivateFn } from '@angular/router'; interface de exportação DirtyFormComponent {isDirty(): boolean; } export const dirtyFormGuard: CanDeactivateFn = componente => { if (!component.isDirty()) return true; return window.confirm('Você tem alterações não salvas que serão perdidas. Continuar?'); }; Componente com formulário responsivo // features/profile/profile-edit.component.ts import { Component, OnInit } from '@angular/core'; importar { NonNullableFormBuilder, ReactiveFormsModule, Validators } de '@angular/forms'; importar { DirtyFormComponent } de '../../core/guards/dirty-form.guard'; @Component({ selector: 'app-profile-edit', standalone: ​​​​true, imports: [ReactiveFormsModule], templateUrl: './profile-edit.component.html' }) export class ProfileEditComponent implements OnInit, DirtyFormComponent { readonly form = this.

fb.group({ displayName: ['', [Validators.required, Validators.minLength(2)]], bio: ['', Validators.maxLength(500)], }); private salvoSnapshot = ''; construtor (fb privado: NonNullableFormBuilder) {} ngOnInit(): void { this.savedSnapshot = JSON.stringify(this.form.getRawValue()); } save(): void { this.savedSnapshot = JSON.stringify(this.form.getRawValue()); } isDirty(): boolean { return JSON.stringify(this.form.getRawValue()) !== this.savedSnapshot; } } Seção 4: Interceptores HTTP — Segurança entre canais Os interceptadores HTTP interceptam todas as solicitações HTTP de saída e todas as respostas recebidas. Eles são o local ideal para adicionar o token JWT, gerenciar atualizações automáticas e centralizar a lógica de tratamento de erros. Interceptor de autenticação JWT // core/interceptors/auth.interceptor.ts importar { HttpInterceptorFn, HttpErrorResponse } de '@angular/common/http'; importar {injetar} de '@angular/core'; importar {catchError, throwError} de 'rxjs'; importar { AuthService } de '../services/auth.service'; importar {Roteador} de '@angular/router'; exportar const authInterceptor: HttpInterceptorFn = (req, próximo) => { const auth = inject(AuthService); const roteador = injetar(Roteador); token const = auth.getToken(); const autorizadoReq = token? req.clone({ setHeaders: { Autorização: `Bearer ${token}` } }) : req; return next(authorizedReq).pipe( catchError((err: HttpErrorResponse) => { if (err.status === 401) { auth.logout(); router.navigate(['/login'], { queryParams: { motivo: 'session-expired' } }); } if (err.status === 403) { router.navigate(['/403']); return throwError(() => err }), }); CarregandoService } de '../services/loading.service'; export const loadingInterceptor: HttpInterceptorFn = (req, próximo) => { if (req.

headers.has('X-Background-Request')) return next(req); const carregando = injetar(LoadingService); carregando.increment(); return next(req).pipe(finalize(() => loading.decrement())); }; Efetuando login em app.config.ts // app.config.ts import { ApplicationConfig } from '@angular/core'; importar { provideRouter, withComponentInputBinding } de '@angular/router'; importar {forneceHttpClient, withInterceptors, withFetch} de '@angular/common/http'; importar {APP_ROUTES} de './app.routes'; importar { authInterceptor } de './core/interceptors/auth.interceptor'; importar {loadingInterceptor} de './core/interceptors/loading.interceptor'; export const appConfig: ApplicationConfig = { provedores: [ provideRouter(APP_ROUTES, withComponentInputBinding()), provideHttpClient(withFetch(), withInterceptors([loadingInterceptor, authInterceptor])), ], }; Melhores práticas para segurança e desempenho Verificação dupla de back-end: guardas protegem a UX, mas cada endpoint da API deve validar independentemente o JWT. Nunca confie apenas no frontend. Tokens de expiração curta: use JWTs com expiração de 15 a 60 minutos combinados com tokens de atualização de expiração longa. Não coloque dados confidenciais no JWT: a carga útil é codificada em Base64, não criptografada. Inclua apenas ID, função e prazo. Configure o CORS corretamente: não use origin: '*' em produção. Use CanMatch para cada módulo sensível de carregamento lento - evita que o pacote seja baixado, e não apenas exibido. Centralize o tratamento de erros HTTP no interceptor: evite duplicar a lógica de redirecionamento de login em cada serviço ou componente. Conclusão A segurança de um aplicativo Angular é construída em camadas. Route Guards ( CanActivate , CanMatch , CanDeactivate ) protegem a navegação e evitam o carregamento de código confidencial. Os interceptadores HTTP garantem que toda comunicação com o backend ocorra de maneira autenticada.

Lembre-se: a segurança do frontend é a primeira linha de defesa do usuário, mas a verdadeira segurança está no backend. Use ambos os níveis e seu sistema será robusto e profissional.

💬 Notas dos leitores

0 notas

Escreva uma nota

Partilhe a sua opinião, uma sugestão ou um elogio

Notas recentes

Ainda não há notas. Seja o primeiro a comentar!