DEV Community

Cover image for Autenticação no React com Context API e Hooks
Rafael Domingues
Rafael Domingues

Posted on

Autenticação no React com Context API e Hooks

O que é Context API?

A Context API, fornece uma maneira de passar os dados de componentes sem ter que passar manualmente em todos os níveis. Algo como o que o Redux faz criando e gerenciando um estado global, inclusive o próprio Redux utiliza Context API por debaixo dos panos.

Vale a pena dar uma olhada em como funciona e utilizar nos seus projetos para manipular dados simples, como na autenticação de um usuário em que veremos neste post.

Criando novo projeto

Primeiramente vou criar um novo projeto seguindo a documentação do ReactJS com Typescript (caso não deseje utilizar Typescript pode seguir normalmente o tutorial e ignorar as declarações de tipos), então vou executar o seguinte comando no meu terminal:

$ npx create-react-app authapp --template typescript

Com o projeto criado e aberto no seu editor preferido vamos começar apagando todos os arquivos que o React cria automaticamente, ficando com a seguinte estrutura:

estrutura

Lembre-se de apagar as linhas de código que dependiam dos arquivos deletados!

Agora vamos instalar uma lib para nos auxiliar a lidar com as rotas da aplicação, para esse tutorial estarei utilizando a React Router. Podemos instalar através do seguinte comando:

$ yarn add react-router-dom

Caso esteja utilizando Typescript igual a mim, vai precisar instalar também a definição de tipos para essa lib como dependência de desenvolvimento através do comando:

$ yarn add @types/react-router-dom -D

Paginas da aplicação

Com a lib instalada podemos continuar, vamos agora criar dentro de src uma pasta chamada pages contendo 2 outras pastas, Login e Home cada uma com um arquivo index.tsx dentro, que serão as páginas da nossa aplicação. Por enquanto estamos assim:

Alt Text

Para ser mais rápido nesse tutorial não vou criar nenhum tipo de estilo para as páginas, mas fique a vontade para isso! Em nossa página Home vamos criar um componente contendo somente um h1 com o nome da página:

import React from 'react';

const Home: React.FC = () => {
 return (
   <div>
     <h1>Home</h1>
   </div>
 );
};

export default Home;

Em nossa página de Login vamos criar somente um botão que será responsável pelo nosso Login:

import React from 'react';

const Login: React.FC = () => {
 function handleLogin() {}

 return (
   <div>
     <button onClick={handleLogin}>Login</button>
   </div>
 );
};

export default Login;

Rotas da aplicação

Com as páginas criadas, vamos agora criar as rotas da nossa aplicação. Primeiro vamos criar,dentro de src, uma pasta routes onde vamos criar os arquivos que serão nossa rotas.

Para esse tutorial criei um arquivo que será responsável pelas rotas em que o usuário poderá se autenticar, tais como Login, Sign Up, etc, e um outro arquivo que será responsável pela navegação depois do usuário já estar autenticado. Ficando com a seguinte estrutura:

Alt Text

No nosso arquivo SignRoutes.tsx vamos criar a rota para nossa página de login seguindo a documentação do React Router.

import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';

import Login from '../pages/Login';

const SignRoutes: React.FC = () => {
 return (
   <BrowserRouter>
     <Route path="/" component={Login} />
   </BrowserRouter>
 );
};

export default SignRoutes;

Vamos fazer o mesmo para nosso OtherRoutes.tsx mas dessa vez utilizando nossa página Home:

import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';

import Home from '../pages/Home';

const OtherRoutes: React.FC = () => {
 return (
   <BrowserRouter>
     <Route path="/" component={Home} />
   </BrowserRouter>
 );
};

export default OtherRoutes;

Agora em nosso index.tsx, ainda na pasta, routes vamos importar nossas rotas e por enquanto retornar somente nossa rota de Login:

import React from 'react';

import SignRoutes from './SignRoutes';
import OtherRoutes from './OtherRoutes';

const Routes: React.FC = () => {
 return <SignRoutes />;
};

export default Routes;

Agora em nosso App.tsx na raiz do nosso projeto importaremos nossas Rotas, assim:

import React from 'react';
import Routes from './routes';

function App() {
 return <Routes />;
}

export default App;

Se rodarmos yarn start no nosso terminal poderemos ver nossa página de Login com um botão:

Alt Text

Criando Contexto

Com a base da nossa aplicação pronta, vamos começar a utilizar o Contexto do React para criar um “estado global” e criar nossa autenticação. Para isso dentro do nosso src vamos criar uma pasta contexts com um arquivo auth.tsx:

Alt Text

Dentro do nosso auth.tsxvamos importar o createContext do React e exportar uma variável AuthContext, um contexto com um objeto vazio dentro:

import React, { createContext } from 'react';

const AuthContext = createContext({});

export default AuthContext;

Em nosso App.tsx vamos importar esse AuthContext e circundar nossas rotas com o Provider do nosso Contexto passando uma propriedade value com um objeto contendo signed: true, da seguinte forma:

import AuthContext from './contexts/auth';

function App() {
 return (
   <AuthContext.Provider value={{signed: true}}>
     <Routes />
   </AuthContext.Provider>
 );
}

Agora se em nossa página de Login buscarmos esse Contexto e dermos um console.log teremos a seguinte resposta:

import React, { useContext } from 'react';
import AuthContext from '../../contexts/auth';

const Login: React.FC = () => {
 const context = useContext(AuthContext);

 console.log(context);
...

Console.log:

Alt Text

Ou seja, nosso signed enviado no nosso App.tsx pode ser recuperado dentro do nosso componente!

Criando Provider

Para melhorar nosso contexto e implementar o restante do código para lidar com a autenticação, vamos trazer o Provider para dentro do nosso arquivo auth.tsx e o exportá-lo.

const AuthContext = createContext({});
...
export const AuthProvider: React.FC = ({ children }) => {
 return (
   <AuthContext.Provider value={{ signed: true }}>
     {children}
   </AuthContext.Provider>
 );
};
...
export default AuthContext;

Agora podemos importar nosso provider dentro do App.tsx, melhorando bastante o nosso código sem alterar o funcionamento:

...
import { AuthProvider } from './contexts/auth';

function App() {
 return (
   <AuthProvider>
     <Routes />
   </AuthProvider>
 );
}
...

Fazendo as chamadas a API

Utilizarei Axios para realizar as requisições à API. Para isso vamos instalar o pacote do axios:

yarn add axios

Vamos criar uma pasta services e um arquivo api.ts para configurar o axios:

import axios from 'axios';

const api = axios.create({
 baseURL: 'https://localhost:3333',
});

export default api;

Com o axios configurado vamos criar uma função para fazer a chamada a api dentro do nosso arquivo auth.tsx:

...
import api from '../services/api';
...
export const AuthProvider: React.FC = ({ children }) => {
...
async function Login() {
   const response = await api.post('/login', {
     email: 'example@email.com',
     password: '123456',
   });

   console.log(response);
 }
...

Para utilizarmos essa função em outros componentes teremos que adicioná-la ao value do nosso Provider:

return (
   <AuthContext.Provider value={{ signed: true, Login }}>
...

Criaremos também uma interface com os dados que estarão no nosso value e adicionaremos o tipo criado ao nosso contexto:

interface AuthContextData {
 signed: boolean;
 Login(): Promise<void>;
}

const AuthContext = createContext<AuthContextData>({} as AuthContextData);

E agora poderemos acessá-la no nosso componente de Login e realizar o login:

...
function handleLogin() {
   context.Login();
}
...

Agora ao clicarmos no botão de login nossa função responsável pelo login será chamada e para ter certeza disso podemos ver nosso console.log:

Alt Text

Precisamos guardar os dados retornados pela API em algum lugar, para isso vamos criar um estado para nosso user e vamos adicionar nosso token no header das nossas chamadas pelo axios:

...
  const [user, setUser] = useState<object | null>(null);
...
  async function Login() {
...
   setUser(response.data.user);
   api.defaults.headers.Authorization = `Bearer ${response.data.token}`
...

Com nosso user em mãos podemos adicioná-lo ao Provider e mudar nosso signed para depender de user:

...
return (
   <AuthContext.Provider value={{ signed: Boolean(user), user, Login }}>
     {children}
   </AuthContext.Provider>
 );
...

Lembre-se de adicionar o user a interface AuthContextData caso esteja utilizando typescript:

interface AuthContextData {
  signed: boolean;
  user: object | null;
  Login(): Promise<void>;
}

Modificando as Rotas

Finalmente podemos ir no index.tsx das nossas rotas e utilizar nosso contexto para decidir qual rota o usuário deve acessar:

import React, { useContext } from 'react';
import AuthContext from '../contexts/auth';

import SignRoutes from './SignRoutes';
import OtherRoutes from './OtherRoutes';

const Routes: React.FC = () => {
 const { signed } = useContext(AuthContext);

 return signed ? <OtherRoutes /> : <SignRoutes />;
};

export default Routes;

Com isso pronto, nossa autenticação já está funcionando e ao clicar em Login o usuário deve ser mandado para a página Home!

Criar hook useAuth

Podemos criar um hook personalizado para facilitar o uso do nosso contexto, para isso vamos exportar uma função chamada useAuth do nosso arquivo auth.tsx, que cria nosso contexto com useContext, e remover nosso export default de AuthContext:

export function useAuth(){
 const context = useContext(AuthContext);

 return context;
}

Agora podemos mudar nas rotas e na nossa página de login onde utilizamos useContext(AuthContext) para:

import { useAuth } from '../../contexts/auth';
...
 const context = useAuth();
...

Finalmente nosso hook para autenticação está pronto para uso!


Extras

Salvar dados no Storage

Normalmente salvamos os dados como user e token para manter o usuário logado mesmo após sair da aplicação. Para isso podemos utilizar o SessionStorage ou LocalStorage, na Web, e o AsyncStorage no React Native.

Na nossa função de login no auth.tsx podemos fazer o seguinte:

async function Login(){
...

  localStorage.setItem('@App:user', JSON.stringify(response.data.user));
  localStorage.setItem('@App:token', response.data.token);
}
...

Para recuperar esses dados podemos criar um useEffect dentro do nosso componente AuthProvider:

...
export const AuthProvider: React.FC = ({ children }) => {
...

useEffect(() => {
    const storagedUser = localStorage.getItem('@App:user');
    const storagedToken = localStorage.getItem('@App:token');

    if (storagedToken && storagedUser) {
      setUser(JSON.parse(storagedUser));
      api.defaults.headers.Authorization = `Bearer ${storagedToken}`;
    }
  }, []);
...

Função de Logout

Como agora estamos salvando os dados no localStorage precisamos de uma forma para fazer logout da aplicação, para isso no nosso Provider dentro de auth.tsx podemos criar uma função que seta user como null novamente e remove os items do localStorage:

...
interface AuthContextData {
  signed: boolean;
  user: object | null;
  Login(user: object): Promise<void>;
  Logout(): void;
}
...
export const AuthProvider: React.FC = ({ children }) => {
...
  function Logout() {
    setUser(null);

    sessionStorage.removeItem('@App:user');
    sessionStorage.removeItem('App:token');
  }

  return (
    <AuthContext.Provider
      value={{ signed: Boolean(user), user, Login, Logout }}
    >
      {children}
    </AuthContext.Provider>
  );
...

Podemos agora criar um botão em nossa página Home e chamar essa função para realizar o logout da aplicação:

const Home: React.FC = () => {
  const { Logout } = useAuth();

  async function handleLogout() {
    Logout();
  }

  return (
    <div>
      <h1>Home</h1>
      <button onClick={handleLogout}>Logout</button>
    </div>
  );
};

Apesar de parecer complicado de inicio, podemos ver que no fim temos algo bem mais simples que Redux e funcionando como deveria! O que achou?

Todo código pode ser encontrado no Github: https://github.com/rafacdomin/Auth-React-ContextAPI

Fonte: https://www.youtube.com/watch?v=KISMYYXSIX8

Top comments (17)

Collapse
 
andersonmav profile image
Anderson Martins Avila

Conteúdo Incrível, me ajudou demais, muito obrigado de verdade!!

Collapse
 
iltonbarbosa profile image
Ilton Barbosa

Em auth.tsx na linha
export const AuthProvider: React.FC = ({ children } ) => {

Está dando o seguinte erro de typescript: A propriedade 'children' não existe no tipo '{}'.

Collapse
 
iltonbarbosa profile image
Ilton Barbosa • Edited

Resolvi da seguinte forma: - criei uma interface:
interface Props {
children: React.ReactNode;
}

E implementei-a assim:
export const AuthProvider: React.FC<Props> = ({ children }: Props ) => {

Collapse
 
domynikmv057 profile image
Domynik Marcone Vieira

Cara ajudou muito obrigado

Collapse
 
fabimendes profile image
Fabi Mendes

Excelente explicação Rafael, muito bom mesmo! Deu tudo certo na minha aplicação!
Só estou com um "probleminha" que talvez você possa ajudar (ainda mais, kkk).
Quando faço o login, a home carrega normalmente, com os dados do usuário no caminho "/", mas quando clico no refresh da página, os dados do usuário desaparecem e só aparecem de novo se eu sair e logar novamente.
Eu chamei os dados do usuário com "const { user } = useAuth();", se eu coloco dentro de um useEffect, não aceita....tem alguma alternativa pra isso?

Collapse
 
fabimendes profile image
Fabi Mendes

Já consegui... lerdeza minha mesmo kkkk
Lá no auth tava fazendo o setItem do @App:user com "response.data.name" kkkk foi só tirar o ".name" e deu certo kkkk
Mas valeu pelo post mesmo assim! Grande ajuda!

Collapse
 
otaviocapila profile image
Otávio Capila

Excelente post, foi de grande ajuda!

Collapse
 
eduardodarocha profile image
Eduardo Rocha

Olá Rafael. Gostei bastante da sua explicação.
Estou utilizando o tutorial para implementar em um projeto mas não uso typescript, então devo simplesmente ignorar a interface?
AuthContextData {
signed: boolean;
Login(): Promise;
}

E essa constante como ficaria?
const AuthContext = createContext({} as AuthContextData);
Obrigado.

Collapse
 
marlondemelo1 profile image
Marlon de Melo

Muito bom o tutorial, fiz baseado nele usando api do Google Login e funcionou de boa consigo puxar até a foto do user nessa api, mas questão de segurança só armazenando token é o recomendado ? Valeuuu

Collapse
 
igorsallo profile image
igorsallo

Cara esse link que vc coloca na API /login, seria do que ? não consegui entender essa parte. Agradeço

Collapse
 
rafacdomin profile image
Rafael Domingues

É só a rota onde faço autenticação na minha api, neste caso localhost:3333 é a url base da minha api e /login é a rota para autenticação. Como já havia definido na configuração no axios a url base como localhost:3333 preciso somente informar a rota que desejo acessar, então digitei /login em vez de localhost:3333/login

Collapse
 
lucasfrutig0 profile image
lucasfrutig0

ao dar refresh eu perco o login =/

Collapse
 
rafacdomin profile image
Rafael Domingues

Verifica a parte sobre Salvar dados no Storage, a ideia é basicamente salvar o token do usuário no localStorage do navegador após o login ser feito e sempre que o usuário acessar a página buscar esse token no localStorage novamente

Collapse
 
gustavomsevero profile image
Gustavo M. Severo • Edited

Cara, o meu auth.tsx ta apontando erro.
Você sabe qual o problema?

Collapse
 
rafacdomin profile image
Rafael Domingues

Qual erro que está acontecendo?

Collapse
 
iltonbarbosa profile image
Ilton Barbosa

Funcionou, mas o usuário só consegue logar após a segunda tentativa de autenticação

Collapse
 
wallacesf profile image
Wallace Ferreira

Fala brother, tudo bem?
Gostaria de sugerir também a verificação da expiração do token. Uma vez que o token foi expirado, o usuário é deslogado automaticamente.