Autenticação básica em apps Ionic: login, tokens e proteção de rotas

Capítulo 11

Tempo estimado de leitura: 9 minutos

+ Exercício

O que é autenticação em um app Ionic (visão prática)

Em um app Ionic (com Angular), autenticação costuma significar: o usuário informa credenciais (ex.: e-mail e senha), o app envia para uma API, a API valida e devolve um token (geralmente JWT). A partir daí, o app usa esse token para acessar rotas protegidas no backend e também controla o acesso a páginas internas (rotas) no frontend.

O fluxo básico fica assim:

  • Login: formulário → API → recebe token (e opcionalmente refresh token).
  • Persistência: guardar token com cuidado (preferencialmente armazenamento seguro).
  • Uso do token: anexar token automaticamente em requisições HTTP (interceptor).
  • Expiração: detectar token expirado/401 → limpar sessão → redirecionar para login.
  • Proteção de rotas: impedir acesso a páginas internas sem sessão válida (guard).

Estrutura recomendada de pastas/arquivos

  • src/app/auth/auth.service.ts (login, logout, estado de sessão)
  • src/app/auth/token-storage.service.ts (armazenamento e leitura do token)
  • src/app/auth/auth.interceptor.ts (anexa token e trata 401)
  • src/app/auth/auth.guard.ts (protege rotas)
  • src/app/pages/login/ (tela de login)

1) Construindo o fluxo de login (formulário → API → token)

Contrato típico da API

Exemplo de endpoints (ajuste para sua API):

  • POST /auth/login com { email, password }
  • Resposta: { accessToken, expiresIn } (e opcionalmente refreshToken)

1.1 Criando o serviço de armazenamento do token

Para apps móveis, prefira armazenamento seguro. Uma abordagem prática é usar @capacitor/preferences como base (simples) e, quando necessário, migrar para um plugin de armazenamento seguro (ex.: Keychain/Keystore). Aqui vamos usar Preferences para manter o exemplo direto.

import { Injectable } from '@angular/core';
import { Preferences } from '@capacitor/preferences';

const ACCESS_TOKEN_KEY = 'access_token';
const EXPIRES_AT_KEY = 'expires_at';

@Injectable({ providedIn: 'root' })
export class TokenStorageService {
  async setSession(accessToken: string, expiresInSeconds?: number) {
    await Preferences.set({ key: ACCESS_TOKEN_KEY, value: accessToken });

    if (expiresInSeconds) {
      const expiresAt = Date.now() + expiresInSeconds * 1000;
      await Preferences.set({ key: EXPIRES_AT_KEY, value: String(expiresAt) });
    } else {
      await Preferences.remove({ key: EXPIRES_AT_KEY });
    }
  }

  async getAccessToken(): Promise<string | null> {
    const { value } = await Preferences.get({ key: ACCESS_TOKEN_KEY });
    return value;
  }

  async getExpiresAt(): Promise<number | null> {
    const { value } = await Preferences.get({ key: EXPIRES_AT_KEY });
    return value ? Number(value) : null;
  }

  async clearSession() {
    await Preferences.remove({ key: ACCESS_TOKEN_KEY });
    await Preferences.remove({ key: EXPIRES_AT_KEY });
  }
}

1.2 Criando o AuthService (login/logout e estado)

O AuthService centraliza: chamada de login, persistência do token, e um estado observável para o app reagir (ex.: menu, redirecionamentos).

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { TokenStorageService } from './token-storage.service';

type LoginResponse = { accessToken: string; expiresIn?: number };

@Injectable({ providedIn: 'root' })
export class AuthService {
  private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
  isAuthenticated$ = this.isAuthenticatedSubject.asObservable();

  constructor(
    private http: HttpClient,
    private tokenStorage: TokenStorageService
  ) {}

  async initFromStorage() {
    const token = await this.tokenStorage.getAccessToken();
    const valid = await this.hasValidSession();
    this.isAuthenticatedSubject.next(!!token && valid);
  }

  async login(email: string, password: string) {
    const res = await firstValueFrom(
      this.http.post<LoginResponse>('/auth/login', { email, password })
    );

    await this.tokenStorage.setSession(res.accessToken, res.expiresIn);
    this.isAuthenticatedSubject.next(true);
  }

  async logout() {
    await this.tokenStorage.clearSession();
    this.isAuthenticatedSubject.next(false);
  }

  async hasValidSession(): Promise<boolean> {
    const token = await this.tokenStorage.getAccessToken();
    if (!token) return false;

    const expiresAt = await this.tokenStorage.getExpiresAt();
    if (!expiresAt) return true; // se a API não envia expiração, trate como "desconhecida"

    return Date.now() < expiresAt;
  }
}

Dica: chame authService.initFromStorage() no bootstrap do app (por exemplo, no AppComponent) para restaurar a sessão ao abrir o aplicativo.

1.3 Tela de Login (formulário + chamada do AuthService)

Exemplo de lógica do componente (foco no fluxo):

import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from 'src/app/auth/auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.page.html'
})
export class LoginPage {
  email = '';
  password = '';
  loading = false;
  errorMsg = '';

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

  async onSubmit() {
    this.loading = true;
    this.errorMsg = '';

    try {
      await this.auth.login(this.email, this.password);
      await this.router.navigateByUrl('/home', { replaceUrl: true });
    } catch (err: any) {
      this.errorMsg = 'Falha no login. Verifique suas credenciais.';
    } finally {
      this.loading = false;
    }
  }
}

Exemplo de template (resumo):

<ion-content class="ion-padding">
  <ion-item>
    <ion-input label="E-mail" type="email" [(ngModel)]="email"></ion-input>
  </ion-item>

  <ion-item>
    <ion-input label="Senha" type="password" [(ngModel)]="password"></ion-input>
  </ion-item>

  <p *ngIf="errorMsg">{{ errorMsg }}</p>

  <ion-button expand="block" [disabled]="loading" (click)="onSubmit()">
    Entrar
  </ion-button>
</ion-content>

2) Anexando o token automaticamente nas requisições (Interceptor)

Em vez de passar o token manualmente em cada serviço, um interceptor adiciona o header Authorization: Bearer ... em todas as requisições (ou em um subconjunto).

2.1 Interceptor de autenticação

import { Injectable } from '@angular/core';
import {
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, from, throwError } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';
import { TokenStorageService } from './token-storage.service';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(
    private tokenStorage: TokenStorageService,
    private auth: AuthService,
    private router: Router
  ) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Evite anexar token em endpoints públicos (ex.: login)
    const isAuthEndpoint = req.url.includes('/auth/login');

    return from(this.tokenStorage.getAccessToken()).pipe(
      switchMap((token) => {
        const authReq = (!isAuthEndpoint && token)
          ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
          : req;

        return next.handle(authReq).pipe(
          catchError((err: HttpErrorResponse) => {
            if (err.status === 401) {
              // Sessão inválida/expirada
              return from(this.handleUnauthorized()).pipe(
                switchMap(() => throwError(() => err))
              );
            }
            return throwError(() => err);
          })
        );
      })
    );
  }

  private async handleUnauthorized() {
    await this.auth.logout();
    await this.router.navigateByUrl('/login', { replaceUrl: true });
  }
}

2.2 Registrando o interceptor

Registre no módulo principal (ou no providers do bootstrap, dependendo da sua estrutura):

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth/auth.interceptor';

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]

3) Expiração de sessão: como detectar e reagir

Existem dois sinais comuns de expiração:

  • Tempo local: você guarda expiresAt e considera inválido quando Date.now() > expiresAt.
  • Resposta do servidor: o backend retorna 401 Unauthorized e você encerra a sessão.

3.1 Bloquear acesso quando o token já expirou (antes de chamar API)

Você pode usar hasValidSession() no guard (ver seção de rotas) para impedir que o usuário entre em páginas protegidas quando o token já passou do prazo.

3.2 Encerrar sessão ao receber 401

O interceptor acima já faz isso: ao receber 401, ele executa logout() e redireciona para /login. Isso evita que o usuário continue navegando com uma sessão inválida.

4) Proteção de rotas com Guard e redirecionamento

O guard decide se uma rota pode ser ativada. Aqui, a regra é: se não houver sessão válida, redirecionar para login. Também é útil guardar a URL de destino para voltar após autenticar.

4.1 Implementando o AuthGuard

import { Injectable } from '@angular/core';
import { CanActivate, Router, UrlTree, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(private auth: AuthService, private router: Router) {}

  async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean | UrlTree> {
    const ok = await this.auth.hasValidSession();
    if (ok) return true;

    return this.router.createUrlTree(['/login'], {
      queryParams: { redirectTo: state.url }
    });
  }
}

4.2 Aplicando o guard nas rotas

Proteja páginas internas (ex.: /home, /profile):

const routes = [
  { path: 'login', loadChildren: () => import('./pages/login/login.module').then(m => m.LoginPageModule) },
  {
    path: 'home',
    canActivate: [AuthGuard],
    loadChildren: () => import('./pages/home/home.module').then(m => m.HomePageModule)
  }
];

4.3 Redirecionar para a rota original após login

No login, leia redirectTo e navegue para lá após autenticar.

import { ActivatedRoute, Router } from '@angular/router';

constructor(
  private auth: AuthService,
  private router: Router,
  private route: ActivatedRoute
) {}

async onSubmit() {
  // ... login
  const redirectTo = this.route.snapshot.queryParamMap.get('redirectTo') || '/home';
  await this.router.navigateByUrl(redirectTo, { replaceUrl: true });
}

5) Logout e limpeza de dados

Logout deve remover token e quaisquer dados derivados da sessão (cache de usuário, preferências sensíveis, etc.). Um padrão simples é expor um botão de sair em uma página de configurações e chamar auth.logout() + redirecionar.

async onLogout() {
  await this.auth.logout();
  await this.router.navigateByUrl('/login', { replaceUrl: true });
}

6) Cuidados básicos de segurança (essenciais no dia a dia)

Armazenamento do token

  • Prefira armazenamento seguro (Keychain/Keystore) para tokens de longa duração. Preferences é prático, mas não é o nível mais forte de proteção.
  • Evite guardar senha no dispositivo.
  • Guarde o mínimo: token e expiração. Dados sensíveis do usuário (documentos, cartões, etc.) devem ser evitados no storage local.

Exposição do token

  • Nunca logue o token em console.log (principalmente em builds de produção).
  • Não coloque token em query string (URLs podem vazar em logs, histórico e analytics). Use header Authorization.
  • Use HTTPS sempre. Token em HTTP pode ser interceptado.

Tempo de vida e revogação

  • Tokens curtos reduzem impacto em caso de vazamento.
  • Se sua API suportar, use refresh token (armazenado com ainda mais cuidado) para renovar sessão sem pedir senha o tempo todo.
  • Considere endpoint de logout no servidor para revogar refresh tokens (quando existir).

Proteção de telas e dados em memória

  • Mesmo com guard, evite carregar dados sensíveis antes de validar sessão.
  • Ao receber 401, limpe caches em memória relacionados ao usuário.

7) Checklist rápido do fluxo funcionando

ItemComo validar
Login salva tokenApós login, getAccessToken() retorna valor
Interceptor anexa AuthorizationRequisições protegidas chegam no backend com Bearer
Rotas protegidas bloqueiam sem sessãoAcessar /home sem token redireciona para /login
Expiração encerra sessãoToken expirado ou 401 → logout + redirecionamento
Redirecionamento pós-loginAcessar rota protegida → login → volta para a rota original

Agora responda o exercício sobre o conteúdo:

Em um app Ionic com Angular, qual é a forma recomendada de garantir que o token de autenticação seja enviado automaticamente nas requisições para endpoints protegidos e, ao mesmo tempo, reagir quando a sessão se tornar inválida?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

O interceptor centraliza o envio do token no header Authorization e permite tratar respostas 401. Ao detectar sessão inválida/expirada, a prática indicada é limpar a sessão com logout e redirecionar para o login.

Próximo capitúlo

Armazenamento local no Ionic: persistência com Storage e preferências

Arrow Right Icon
Capa do Ebook gratuito Ionic para Iniciantes: aplicativos híbridos com HTML, CSS e TypeScript
52%

Ionic para Iniciantes: aplicativos híbridos com HTML, CSS e TypeScript

Novo curso

21 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.