Como Usar JWT com Vite e React-TS: Um Guia de Autenticação

Posted by

A autenticação baseada em JWT (JSON Web Token) é uma solução amplamente utilizada para proteger aplicações modernas. Combinando Vite, React com TypeScript (React-TS) e JWT, é possível construir uma aplicação front-end performática, bem estruturada e segura.

Neste guia, você aprenderá como configurar uma aplicação React-TS com Vite para se comunicar com um backend que utiliza JWT para autenticação, incluindo a atualização automática de tokens.

O Que É JWT?

JWT (JSON Web Token) é um padrão aberto usado para transmitir informações entre partes de forma segura. Ele é composto por três partes principais: header, payload e signature. Essa estrutura permite verificar a autenticidade do token e garantir que os dados não foram alterados durante o transporte.

O uso de JWT é comum em APIs REST, pois permite autenticar usuários sem armazenar sessões no servidor, tornando o sistema mais escalável.

Por Que Usar Vite + React-TS?

Vite é uma ferramenta de build altamente eficiente, projetada para melhorar a produtividade no desenvolvimento front-end. Quando combinada com React e TypeScript, oferece:

  • Desenvolvimento rápido com Hot Module Replacement
  • Suporte nativo ao TypeScript
  • Estrutura modular e escalável

Essas vantagens fazem do conjunto Vite + React-TS uma excelente escolha para integrar com backends que usam JWT para autenticação.

Configurando a Comunicação com o Backend

Para implementar a autenticação com JWT na sua aplicação front-end, você precisa seguir alguns passos básicos:

  1. Fazer login no backend e receber um access_token
  2. Armazenar esse token localmente
  3. Incluir o token nos cabeçalhos das requisições HTTP para acessar rotas protegidas
  4. Gerenciar a atualização do token com refresh_token

Estrutura Inicial da Aplicação

Primeiro, crie seu projeto com Vite e React-TS:

npm create vite@latest my-app --template react-ts
cd my-app
npm install

Instale também dependências úteis para chamadas HTTP:

npm install axios jwt-decode

Criando o Serviço de Autenticação

Crie um arquivo src/services/auth.service.ts para lidar com as requisições de login e token:

import axios from 'axios'

const API_URL = 'http://localhost:3000'

const apiClient = axios.create({
  baseURL: API_URL
})

export const login = async (username: string, password: string) => {
  const response = await apiClient.post('/login', { username, password })
  if (response.data.accessToken) {
    localStorage.setItem('user', JSON.stringify(response.data))
  }
  return response.data
}

export const logout = () => {
  localStorage.removeItem('user')
}

export const refreshToken = async () => {
  const user = JSON.parse(localStorage.getItem('user') || '{}')
  const response = await apiClient.post('/refresh-token', {
    refreshToken: user.refreshToken
  })
  const updatedUser = {
    ...user,
    accessToken: response.data.accessToken
  }
  localStorage.setItem('user', JSON.stringify(updatedUser))
  return updatedUser
}

export const getCurrentUser = () => {
  return JSON.parse(localStorage.getItem('user') || 'null')
}

Middleware para Requisições Protegidas

Você pode criar um interceptor do Axios para adicionar automaticamente o access_token nos cabeçalhos das requisições:

// src/interceptors/auth.interceptor.ts
import axios from 'axios'
import { refreshToken } from '../services/auth.service'

const setupInterceptors = () => {
  axios.interceptors.response.use(
    (response) => response,
    async (error) => {
      const originalRequest = error.config

      if (error.response.status === 401 && !originalRequest._retry) {
        originalRequest._retry = true

        try {
          await refreshToken()
          return axios(originalRequest)
        } catch (err) {
          console.error('Falha ao renovar token:', err)
          return Promise.reject(err)
        }
      }

      return Promise.reject(error)
    }
  )
}

export default setupInterceptors

No seu main.tsx, chame o interceptor:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import setupInterceptors from './interceptors/auth.interceptor'

setupInterceptors()

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

Gerenciando Rotas Protegidas

Você pode criar um componente de rota protegida para garantir que apenas usuários autenticados acessem certas páginas:

// src/components/ProtectedRoute.tsx
import { Navigate, Outlet } from 'react-router-dom'
import { getCurrentUser } from '../services/auth.service'

const ProtectedRoute = () => {
  const user = getCurrentUser()
  return user ? <Outlet /> : <Navigate to="/login" />
}

export default ProtectedRoute

Use-o nas rotas da seguinte forma:

// src/router.tsx
import { createBrowserRouter } from 'react-router-dom'
import ProtectedRoute from './components/ProtectedRoute'
import Home from './pages/Home'
import Login from './pages/Login'

const router = createBrowserRouter([
  {
    path: '/',
    element: <ProtectedRoute />,
    children: [
      {
        index: true,
        element: <Home />
      }
    ]
  },
  {
    path: '/login',
    element: <Login />
  }
])

export default router

Implementando a Página de Login

// src/pages/Login.tsx
import React, { useState } from 'react'
import { login } from '../services/auth.service'
import { useNavigate } from 'react-router-dom'

const Login = () => {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const navigate = useNavigate()

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault()
    try {
      await login(username, password)
      navigate('/')
    } catch (err) {
      alert('Erro no login')
    }
  }

  return (
    <div>
      <h2>Login</h2>
      <form onSubmit={handleLogin}>
        <input
          type="text"
          placeholder="Usuário"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
        <input
          type="password"
          placeholder="Senha"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        <button type="submit">Entrar</button>
      </form>
    </div>
  )
}

export default Login

Estrutura Final do Projeto

Seu projeto terá uma estrutura semelhante a esta:

my-app/
├── public/
├── src/
│   ├── components/
│   │   └── ProtectedRoute.tsx
│   ├── interceptors/
│   │   └── auth.interceptor.ts
│   ├── pages/
│   │   ├── Login.tsx
│   │   └── Home.tsx
│   ├── services/
│   │   └── auth.service.ts
│   ├── router.tsx
│   ├── main.tsx
│   └── App.tsx
├── package.json
└── tsconfig.json

Considerações sobre Segurança

Armazenar tokens JWT no localStorage não é seguro, pois permite que scripts maliciosos acessem esses dados via XSS (Cross-Site Scripting). Uma alternativa mais segura é utilizar cookies com os atributos HttpOnly e Secure para armazenar refresh tokens, enquanto o access token é mantido temporariamente em memória.

Além disso, recomenda-se:

  • Utilizar HTTPS para todas as comunicações entre cliente e servidor
  • Definir tempos curtos de expiração para tokens
  • Implementar mecanismos de invalidação de tokens no backend
  • Evitar armazenar informações sensíveis no lado do cliente

Conclusão

Combinar Vite, React-TS e JWT é uma escolha poderosa para construir aplicações front-end modernas, rápidas e seguras. Ao usar refresh tokens e interceptores HTTP, você melhora a experiência do usuário mantendo a segurança e evitando reautenticações frequentes.

Este guia cobriu desde a criação do projeto até a configuração completa de autenticação com JWT, incluindo armazenamento seguro, renovação automática de token e rotas protegidas. Agora você está pronto para implementar essas práticas em seus projetos reais.

Leave a Reply

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *