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/logincom{ email, password }- Resposta:
{ accessToken, expiresIn }(e opcionalmenterefreshToken)
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).
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
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
expiresAte considera inválido quandoDate.now() > expiresAt. - Resposta do servidor: o backend retorna
401 Unauthorizede 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
| Item | Como validar |
|---|---|
| Login salva token | Após login, getAccessToken() retorna valor |
| Interceptor anexa Authorization | Requisições protegidas chegam no backend com Bearer |
| Rotas protegidas bloqueiam sem sessão | Acessar /home sem token redireciona para /login |
| Expiração encerra sessão | Token expirado ou 401 → logout + redirecionamento |
| Redirecionamento pós-login | Acessar rota protegida → login → volta para a rota original |