Autenticação é uma daquelas coisas que sempre parece exigir muito mais esforço do que queremos. Para configurar a autenticação, é preciso pesquisar novamente tópicos sobre os quais você não pensou desde a última vez em que fez a autenticação, e a natureza acelerada do espaço significa que as coisas mudaram com frequência nesse meio tempo. Novas ameaças, novas opções e novas atualizações podem tê-lo deixado na dúvida e vasculhando documentos em seus projetos anteriores.
Neste artigo, apresentamos uma abordagem diferente para autenticação (e controle de acesso, SSO e muito mais) em aplicativos React. Em vez de adicionar uma biblioteca estática que você precisa manter atualizada ou pesquisar novamente sempre que quiser implementar a autenticação, usaremos um serviço que se mantém atualizado automaticamente e é uma alternativa muito mais simples ao Auth0, Okta e outros.
Autenticação React
Normalmente, usamos uma abordagem semelhante ao escrever a autenticação no React: nosso aplicativo React faz uma solicitação ao nosso servidor de autenticação, que retorna um token de acesso. Esse token é salvo no navegador e pode ser usado em solicitações subsequentes ao seu servidor (ou a outros servidores, se necessário). Seja escrevendo um e-mail e um carimbo padrão; autenticação por senha ou usando links mágicos ou logins de logon único (SSO) como Google, Azure ou Facebook, queremos que nosso aplicativo React envie uma solicitação inicial a um servidor de autenticação e que esse servidor lide com toda a complexidade da geração de um token.
Portanto, a responsabilidade do React na autenticação é:
- Enviar a solicitação inicial para o servidor de autenticação
- Receber e armazenar o token de acesso
- Enviar o token de acesso ao seu servidor com cada solicitação subsequente
Tokens de acesso JWT
Os JSON Web Tokens (JWTs) são tokens compactos e seguros para URL que podem ser usados para autenticação e controle de acesso em aplicativos React. Cada JWT tem um objeto JSON simples como sua “carga útil” e é assinado de forma que o servidor possa verificar se a carga útil é autêntica. Um exemplo de JWT seria parecido com:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImF1dGhvcml6YXRpb24iOiJhZG1pbiJ9.f7iKN-xi24qrQ5NQtOe0jiriotT-rve3ru6sskbQXnA
A carga desse token é a seção do meio (separada por pontos):
eyJ1c2VySWQiOjEsImF1dGhvcml6YXRpb24iOiJhZG1pbiJ9
A carga útil do JWT pode ser decodificada a partir da base64 para gerar o objeto JSON:
JSON.parse(atob("eyJ1c2VySWQiOjEsImF1dGhvcml6YXRpb24iOiJhZG1pbiJ9")); // => { “userId”: 1, “authorization”: “admin” }
É importante observar que essa carga útil pode ser lida por qualquer pessoa com o JWT, incluindo seu aplicativo React ou um terceiro.
Qualquer pessoa que tenha o JWT pode ler seu conteúdo. No entanto, somente o servidor de autenticação pode gerar JWTs válidos – seu aplicativo React, seu servidor de aplicativos ou um terceiro mal-intencionado não podem gerar JWTs válidos. Portanto, além de ler o JWT, o servidor também precisa verificar se o JWT é autêntico, comparando-o com uma chave pública. Isso permite que o servidor de aplicativos verifique os JWTs recebidos e rejeite quaisquer tokens que não tenham sido criados pelo servidor de autenticação ou que tenham expirado.
O fluxo para usar um JWT em um aplicativo React tem a seguinte aparência:
- Seu aplicativo React solicita um JWT sempre que o usuário quiser se conectar.
- O servidor de autenticação gera um JWT usando uma chave privada e, em seguida, envia o JWT de volta ao seu aplicativo React.
- O aplicativo React armazena esse JWT e o envia ao servidor de aplicativos sempre que o usuário precisar fazer uma solicitação.
- O servidor de aplicativos verifica o JWT usando uma chave pública e, em seguida, lê a carga útil para determinar qual usuário está fazendo a solicitação.
Cada uma dessas etapas é simples de escrever, mas cada etapa tem suas próprias armadilhas quando o senhor realmente deseja implementá-la e mantê-la segura. Especialmente com o passar do tempo, à medida que surgem novos vetores de ameaças e novas plataformas precisam ser corrigidas ou receber suporte, a sobrecarga de segurança pode aumentar rapidamente.
Userfront elimina a complexidade da autenticação em aplicativos React
O Userfront é uma estrutura que abstrai a complexidade da autenticação. Isso facilita muito o trabalho com a autenticação em um aplicativo React e, talvez o mais importante, mantém todos os protocolos de autenticação atualizados para você automaticamente ao longo do tempo.
A filosofia subjacente do Userfront é que a autenticação de classe mundial não deve exigir esforço – deve ser fácil de configurar, e as atualizações de segurança devem ocorrer automaticamente para o usuário. O Userfront tem todos os recursos de autenticação, Single Sign On (SSO), controle de acesso e multilocação, com uma camada gratuita pronta para produção de até 10.000 usuários ativos mensais. Para a maioria dos aplicativos React modernos, é uma ótima solução.
Configurando a autenticação no React
Agora, vamos analisar a criação de todos os principais aspectos da autenticação em um aplicativo React. O código final para este exemplo está disponível aqui.
Use seu boilerplate favorito para configurar seu aplicativo React e colocar seu pipeline de compilação em ordem. Neste artigo, usaremos o Criar aplicativo Reactque faz grande parte do trabalho de configuração para nós, e também adicionaremos Roteador React para nosso roteamento no lado do cliente. Comece instalando o Create React App e o React Router:
npx create-react-app my-app cd my-app npm install react-router-dom --save npm start
Agora nosso aplicativo React está disponível em http://localhost:3000
Como ele diz, agora podemos editar o arquivo src/App.js
para começar a trabalhar.
Substitua o conteúdo de src/App.js
pelo seguinte, com base no início rápido do React Router:
// src/App.js import React from "react"; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; export default function App() { return ( <Router> <div> <nav> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/login">Login</Link> </li> <li> <Link to="/reset">Reset</Link> </li> <li> <Link to="/dashboard">Dashboard</Link> </li> </ul> </nav> <Switch> <Route path="/login"> <Login /> </Route> <Route path="/reset"> <PasswordReset /> </Route> <Route path="/dashboard"> <Dashboard /> </Route> <Route path="/"> <Home /> </Route> </Switch> </div> </Router> ); } function Home() { return <h2>Home</h2>; } function Login() { return <h2>Login</h2>; } function PasswordReset() { return <h2>Password Reset</h2>; } function Dashboard() { return <h2>Dashboard</h2>; }
Agora temos um aplicativo muito simples com roteamento:
Roteamento | Descrição |
---|---|
/ | Página inicial |
/login | Página de login |
/reset | Página de redefinição de senha |
/dashboard | Painel de controle do usuário, somente para usuários conectados |
Essa é toda a estrutura de que precisamos para começar a adicionar a autenticação.
Inscrição, login e redefinição de senha com o Userfront
Primeiro, crie uma conta do Userfront em https://userfront.com. Isso lhe dará um formulário de inscrição, um formulário de login e um formulário de redefinição de senha que o senhor poderá usar nas próximas etapas.
Na seção Toolkit (Kit de ferramentas) do painel do Userfront, você encontrará as instruções para instalar o formulário de inscrição:
Siga as instruções instalando o pacote de reação do Userfront com:
npm install @userfront/react --save npm start
Em seguida, adicione o formulário à sua página inicial importando e inicializando o Userfront e, depois, atualizando o Home()
para renderizar o formulário.
// src/App.js import React from "react"; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; import Userfront from "@userfront/react"; Userfront.init("demo1234"); const SignupForm = Userfront.build({ toolId: "nkmbbm", }); export default function App() { return ( <Router> <div> <nav> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/login">Login</Link> </li> <li> <Link to="/reset">Reset</Link> </li> <li> <Link to="/dashboard">Dashboard</Link> </li> </ul> </nav> <Switch> <Route path="/login"> <Login /> </Route> <Route path="/reset"> <PasswordReset /> </Route> <Route path="/dashboard"> <Dashboard /> </Route> <Route path="/"> <Home /> </Route> </Switch> </div> </Router> ); } function Home() { return ( <div> <h2>Home</h2> <SignupForm /> </div> ); } function Login() { return <h2>Login</h2>; } function PasswordReset() { return <h2>Password Reset</h2>; } function Dashboard() { return <h2>Dashboard</h2>; }
Agora a página inicial tem seu formulário de inscrição. Tente inscrever um usuário:
O formulário está no “Modo de teste” por padrão, o que criará registros de usuários em um ambiente de teste que pode ser visualizado separadamente no painel do Userfront:
Continue adicionando seus formulários de login e redefinição de senha da mesma forma que adicionou o formulário de inscrição:
// src/App.js import React from "react"; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; import Userfront from "@userfront/react"; Userfront.init("demo1234"); const SignupForm = Userfront.build({ toolId: "nkmbbm", }); const LoginForm = Userfront.build({ toolId: "alnkkd", }); const PasswordResetForm = Userfront.build({ toolId: "dkbmmo", }); export default function App() { return ( <Router> <div> <nav> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/login">Login</Link> </li> <li> <Link to="/reset">Reset</Link> </li> <li> <Link to="/dashboard">Dashboard</Link> </li> </ul> </nav> <Switch> <Route path="/login"> <Login /> </Route> <Route path="/reset"> <PasswordReset /> </Route> <Route path="/dashboard"> <Dashboard /> </Route> <Route path="/"> <Home /> </Route> </Switch> </div> </Router> ); } function Home() { return ( <div> <h2>Home</h2> <SignupForm /> </div> ); } function Login() { return ( <div> <h2>Login</h2> <LoginForm /> </div> ); } function PasswordReset() { return ( <div> <h2>Password Reset</h2> <PasswordResetForm /> </div> ); } function Dashboard() { return <h2>Dashboard</h2>; }
Nesse ponto, a inscrição, o login e a redefinição de senha devem estar funcionando.
Seus usuários podem se inscrever, fazer login e redefinir a senha.
Rota protegida no React
Normalmente, não queremos que os usuários consigam visualizar o painel a menos que estejam conectados. Isso é conhecido como proteger uma rota.
Sempre que um usuário não estiver conectado, mas tentar visitar o /dashboard
, podemos redirecioná-lo para a tela de login.
Podemos fazer isso atualizando o Dashboard
no componente src/App.js
para lidar com a lógica condicional.
Quando um usuário estiver conectado ao Userfront, ele terá um token de acesso disponível como Userfront.accessToken()
. Podemos verificar esse token para determinar se o usuário está conectado.
Adicione o Redirect
ao componente import
para o React Router e, em seguida, atualize a declaração Dashboard
para redirecionar se nenhum token de acesso estiver presente.
// src/App.js import React from "react"; import { BrowserRouter as Router, Switch, Route, Link, Redirect, // Be sure to add this import } from "react-router-dom"; // ... function Dashboard() { function renderFn({ location }) { // If the user is not logged in, redirect to login if (!Userfront.accessToken()) { return ( <Redirect to={{ pathname: "/login", state: { from: location }, }} /> ); } // If the user is logged in, show the dashboard const userData = JSON.stringify(Userfront.user, null, 2); return ( <div> <h2>Dashboard</h2> <pre>{userData}</pre> <button onClick={Userfront.logout}>Logout</button> </div> ); } return <Route render={renderFn} />; }
Observe também que adicionamos um botão de logout chamando Userfront.logout()
diretamente:
<button onClick={Userfront.logout}>Logout</button>
Agora, quando um usuário estiver conectado, ele poderá visualizar o painel. Se o usuário não estiver conectado, ele será redirecionado para a página de login.
Autenticação React com uma API
O senhor provavelmente desejará recuperar informações específicas do usuário do seu backend. Para proteger esses pontos de extremidade da API, seu servidor deve verificar se os JWTs recebidos são válidos.
Há muitas bibliotecas para ler e verificar JWTs em vários idiomas; aqui estão algumas bibliotecas populares para lidar com JWTs:
Para o Userfront, o token de acesso está disponível no seu aplicativo React como Userfront.accessToken()
.
Seu aplicativo React pode enviar isso como um Bearer
dentro do token Authorization
header. Por exemplo:
// Example of calling an endpoint with a JWT async function getInfo() { const res = await window.fetch("/your-endpoint", { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${Userfront.accessToken()}`, }, }); console.log(res); } getInfo();
Para lidar com uma solicitação como essa, seu backend deve ler o JWT do cabeçalho Authorization
e verificar se ele é válido usando a chave pública encontrada no painel do Userfront.
Aqui está um exemplo de middleware do Node.js para ler e verificar o JWT:
// Node.js example (Express.js) const jwt = require("jsonwebtoken"); function authenticateToken(req, res, next) { // Read the JWT access token from the request header const authHeader = req.headers["authorization"]; const token = authHeader && authHeader.split(" ")[1]; if (token == null) return res.sendStatus(401); // Return 401 if no token // Verify the token using the Userfront public key jwt.verify(token, process.env.USERFRONT_PUBLIC_KEY, (err, auth) => { if (err) return res.sendStatus(403); // Return 403 if there is an error verifying req.auth = auth; next(); }); }
Usando essa abordagem, todos os tokens inválidos ou ausentes seriam rejeitados pelo seu servidor. O senhor também pode fazer referência ao conteúdo do token mais tarde nos manipuladores de rota usando o req.auth
:
console.log(req.auth); // => { mode: 'test', tenantId: 'demo1234', userId: 1, userUuid: 'ab53dbdc-bb1a-4d4d-9edf-683a6ca3f609', isConfirmed: false, authorization: { demo1234: { tenantId: 'demo1234', name: 'Demo project', roles: ["admin"], permissions: [] }, }, sessionId: '35d0bf4a-912c-4429-9886-cd65a4844a4f', iat: 1614114057, exp: 1616706057 }
Com essas informações, o senhor pode realizar outras verificações, conforme desejado, ou usar o userId
ou userUuid
para procurar informações do usuário para retornar.
Por exemplo, se o senhor quisesse limitar uma rota a usuários administradores, poderia verificar a variável authorization
do token de acesso verificado:
// Node.js example (Express.js) app.get("/users", (req, res) => { const authorization = req.auth.authorization["demo1234"] || {}; if (authorization.roles.includes("admin")) { // Allow access } else { // Deny access } });
React SSO (Single Sign On)
A partir daqui, o senhor pode adicionar provedores de identidade social, como Google, Facebook e LinkedIn, ao seu aplicativo React, ou provedores de identidade comercial, como Azure AD, Office365 e outros.
O senhor faz isso criando um aplicativo com o provedor de identidade (por exemplo, Google) e, em seguida, adicionando as credenciais desse aplicativo ao painel do Userfront. O resultado é uma experiência de login modificada:
Não é necessário nenhum código adicional para implementar o Single Sign On usando essa abordagem: o senhor pode adicionar e remover provedores sem atualizar seus formulários ou a maneira como lida com JWTs.
Notas finais
Adicionar autenticação e controle de acesso ao seu aplicativo React não precisa ser um incômodo. Tanto a etapa de configuração quanto, mais importante, a manutenção ao longo do tempo, são tratadas com plataformas modernas como Userfront.
Os tokens da Web JSON permitem que o senhor separe de forma limpa a camada de geração de tokens de autenticação do restante do aplicativo, facilitando o raciocínio e tornando-o mais modular para necessidades futuras. Essa arquitetura também permite que você concentre seus esforços no aplicativo principal, onde é provável que crie muito mais valor para si mesmo ou para seus clientes.
Para obter mais detalhes sobre adicionar autenticação ao seu aplicativo React, visite a seção Guia do Userfrontque abrange tudo, desde a configuração dos formulários de autenticação até a documentação da API, exemplos de repositórios, trabalho com diferentes linguagens e estruturas e muito mais.