Autenticação ReactJS - (ReactJS, Azure AD)
Tags: react, azure
Published: 2021-03-28 21:24:47

Com o crescimento constante dos sistemas na web cria-se uma necessidade e preocupação com a segurança desses sistemas. Uma forma de criar uma camada de segurança é a criação de autenticação via login.

E uma alternativa de autenticação é o Azure AD (Active Directory), com ele é possível criar configurações específicas para ter essa camada de segurança para seu sistema web.

No Azure AD é possível criar um grupo de usuários e nele adicionar todas as contas de e-mail que podem acessar o sistema web, você pode adicionar qualquer conta que esteja dentro dos domínios da Microsoft, e-mails pessoais e corporativos.

Sendo assim, nesse artigo eu vou demonstrar como fazer as configurações para ativar o Active Directory dentro do Portal do Azure e em seguida as configurações para integrar o Azure AD com o ReactJS.

O primeiro passo é a configuração dentro do [Portal do Azure](https://portal.azure.com/), após entrar no portal é necessário acessar o diretório/assinatura a qual será criada a configuração, em seguida pesquise por Azure Active Directory e selecione, agora selecione a opção **Registros de aplicativo** e clique em **Novo registro**.

Nesse momento irá abrir uma tela para cadastrar algumas informações sobre seu aplicativo, são elas: nome, quem pode usar este aplicativo e URI de redirecionamento que é opcional e finalizamos clicando em **Registrar**.

A imagem abaixo demonstra como ficará esta primeira etapa.
![Registro do aplicativo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/twa0ehx80dry67zfmldu.png)

Assim que o registro do aplicativo for concluído o portal do azure irá redirecionar a página para a visão geral do aplicativo criado, uma página semelhante a imagem abaixo:
![Visão geral do aplicativo criado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zwzmdzy9e8lxzepnztji.png)

Nessa tela o importante a destacar é o **ID do aplicativo (cliente)**, esse código é utilizado como parte da validação de token de segurança entre o sistema web e o Active Directory.

Para começar o projeto ReactJS vamos utilizar o **npx create-react-app my-app** para criar o projeto base da aplicação e logo em seguida criaremos uma pasta **pages** e dentro dela teremos duas pastas para ter rotas diferentes do projeto depois, são elas:

**`src/pages/SignIn/index.js`**
```sh
import React, { useCallback } from 'react';

import { useAuth } from '../../hooks/auth';

import logo from '../../assets/logo.svg';
import '../../assets/styles.css';

const SignIn = () => {
  const { signIn } = useAuth();

  const handleSignIn = useCallback(() => {
    const accessToken = localStorage.getItem('@AzureAd:accessToken');

    if (!accessToken) {
      signIn();
    }
  }, [signIn]);

  return (
    <div className="App">
      <img src={logo} alt="ReactJS, Azure AD" className="App-logo" />

      <button type="button" onClick={handleSignIn}>Entrar</button>
    </div>
  );
};

export default SignIn;
```

**`src/pages/Dashboard/index.js`**
```sh
import React from 'react';

import { useAuth } from '../../hooks/auth';

import logo from '../../assets/logo.svg';
import '../../assets/styles.css';

const Dashboard = () => {
  const { signOut, accountInfo } = useAuth();

  return (
    <div>
      <header className="App-header">
        <img src={logo} alt="ReactJS, Azure AD" className="App-logo" />

        <div>
          <p>Bem-vindo,
            <strong> {accountInfo.user.displayName}</strong>
          </p>
        </div>

        <button type="button" className="App-button" onClick={signOut}>sair</button>
      </header>
    </div>
  );
};

export default Dashboard;
```

Vale notar que nessas duas telas utilizamos o **useAuth** que é um hook que foi criado para ter acesso ao login e informações do usuário logado, mais tarde vamos falar sobre esse hook.

Agora criamos uma pasta **routes**, nela vamos configurar a opção de rotas do projeto e definir qual rota será publica ou privada. A rota privada será acessada somente quando o usuário fizer o login que é autenticado pelo Azure AD.

**`src/routes/Route.js`**
```sh
import React from 'react';
import { Route as ReactDOMRoute, Redirect } from 'react-router-dom';

import { useAuth } from '../hooks/auth';

const Route = ({
  isPrivate = false,
  component: Component,
  ...rest
}) => {
  const { accountInfo } = useAuth();

  return (
    <ReactDOMRoute
      {...rest}
      render={({ location }) => {
        return isPrivate === !!accountInfo.user.email ? (
          <Component />
        ) : (
          <Redirect
            to={{
              pathname: isPrivate ? '/' : '/dashboard',
              state: { from: location },
            }}
          />
        );
      }}
    />
  );
};

export default Route;
```

Na **Route.js** utilizamos o hook useAuth para recuperar as informações do usuário logado e verificar se essas informações são validas a partir do e-mail dele.

Se o e-mail do usuário for valido ele é redirecionado para a tela **Dashboard** onde aparece uma mensagem de boas vindas junto ao nome do usuário que logou e se essa validação do e-mail for invalida o usuário é redirecionado para a tela **SignIn** onde ele pode fazer o login para se autenticar.

**`src/routes/index.js`**
```sh
import React from 'react';
import { Switch } from 'react-router-dom';

import Route from './Route';

import SignIn from '../pages/SignIn';

import Dashboard from '../pages/Dashboard';

const Routes = () => (
  <Switch>
    <Route path="/" exact component={SignIn} />

    <Route path="/dashboard" component={Dashboard} isPrivate />
  </Switch>
);

export default Routes;
```

E para finalizar as configurações de rotas criamos um **index.js** para exportar as configurações de rotas e os componentes que ele irá exibir de acordo com a rota do projeto.

Para isso utilizamos a lib **react-router-dom** que é muito utilizada em projeto ReactJS para fazer configurações de rotas e na página **index.js** configuramos o **<Route>** que foi desenvolvido anteriormente e que recebe os valores de **path** que recebe o nome da rota, **component** que recebe o componente a ser renderizado e por fim o **isPrivate** que define qual rota é privada ou publica, que no nosso caso a rota **/dashboard** será privada.

Após as configurações de páginas e rotas partiremos para criação do **hook useAuth** que vai ter as configurações para efetuar login, logout, acesso ao token e acesso ao usuário que são autenticados no Azure AD.

Para começar vamos criar uma pasta **hooks** e nela teremos algumas arquivos.

No **auth.js** vamos fazer as configurações de comunicação e validação com o Azure AD e utilizaremos a lib **@azure/msal-browser** para fazer isso.

Basicamente no **auth.js** temos quatro métodos: signIn, signOut, getAccessToken, getUserProfile e um useEffect que faz uma primeira validação ao acessar a aplicação para saber se já existe um usuário logado.

**signIn** é um método assíncrono que faz uma chamada para a lib **@azure/msal-browser** abrir o login de usuário em modo **Popup** passando as informações de configurações do Azure Ad que falaremos depois e recuperando esse usuário com o método **getUserProfile**.

**getUserProfile** que é chamado pelo **signIn** envia as informações de request para o **getAccessToken** que retorna se o token é válido ou não, se esse token existir ele chama uma configuração do **graphService** para ter acesso as informações do usuário de acordo com o token, após isso ele salva o token no **localStorage** para utilizar em outro momento e ainda salva um estado utilizando o **useState** com as informações do usuário como **displayName de mail**.

**getAccessToken** faz a validação utilizando o **getAllAccounts** para recuperar o token do usuário e ele tem dois métodos para isso um é o **acquireTokenSilent** que valida o token sem interação do usuário e o outro é o **acquireTokenPopup** que valida o token a partir do momento que o usuário faz o login no Popup que foi aberto pelo sistema.

**signOut** esse método simples executa a remoção do token que foi salvo no localStorage e o logout da lib **@azure/msal-browser** para fazer o logout daquele usuário logado.

E por fim utilizamos o **<AuthContext.Provider>** para exportar os métodos **signIn, signOut, accountInfo** que serão utilizados no projeto, esse último sendo um estado com as informações do usuário.

**`src/hooks/auth.js`**
```sh
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useState,
} from 'react';
import { PublicClientApplication } from '@azure/msal-browser';

import {
  msalConfig,
  loginRequest,
} from '../utils/configAzureAd';
import { getUserDetails } from '../utils/graphService';

const AuthContext = createContext({});

const msalInstance = new PublicClientApplication(msalConfig);

const AuthProvider = ({ children }) => {
  const [accountInfo, setAccountInfo] = useState({
    isAuthenticated: false,
    user: {},
    error: null,
  });

  const signIn = async () => {
    try {
      await msalInstance.loginPopup(
        {
          scopes: loginRequest.scopes,
          prompt: "select_account"
        });

      await getUserProfile();
    }
    catch (err) {
      setAccountInfo({
        isAuthenticated: false,
        user: {},
        error: err,
      });
    }
  }

  const signOut = () => {
    localStorage.removeItem('@AzureAd:accessToken');
    msalInstance.logout();
  }

  const getAccessToken = async (scopes) => {
    try {
      const accounts = msalInstance.getAllAccounts();

      if (accounts.length <= 0) throw new Error('Login required');
      const silentResult = await msalInstance.acquireTokenSilent({
        scopes: scopes,
        account: accounts[0]
      });

      return silentResult.accessToken;
    } catch (err) {
      if (err) {
        const interactiveResult = await msalInstance.acquireTokenPopup({
          scopes: scopes,
        });

        return interactiveResult.accessToken;
      } else {
        throw err;
      }
    }
  }

  const getUserProfile = useCallback(async () => {
    try {
      const accessToken = await getAccessToken(loginRequest.scopes);

      if (accessToken) {
        const user = await getUserDetails(accessToken);

        localStorage.setItem('@AzureAd:accessToken', accessToken);

        setAccountInfo({
          isAuthenticated: true,
          user: {
            displayName: user.displayName,
            email: user.mail || user.userPrincipalName,
          },
          error: null
        });
      }
    }
    catch (err) {
      setAccountInfo({
        isAuthenticated: false,
        user: {},
        error: err,
      });
    }
  }, []);

  useEffect(() => {
    const accounts = msalInstance.getAllAccounts();

    if (accounts && accounts.length > 0) {
      getUserProfile();
    }
  }, [getUserProfile]);

  return (
    <AuthContext.Provider value={{ signIn, signOut, accountInfo }}>
      {children}
    </AuthContext.Provider>
  );
}

const useAuth = () => {
  const context = useContext(AuthContext);

  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }

  return context;
}

export { AuthProvider, useAuth };
```

Um ponto importante do **auth.js** é que ele é criado como **contextAPI** para ser possível repassar suas informações.

**`src/hooks/index.js`**
```sh
import React from 'react';

import { AuthProvider } from './auth';

const AppProvider = ({ children }) => (
  <AuthProvider>
    {children}
  </AuthProvider>
);

export default AppProvider;
```

O **index.js** é importante para exportar os métodos **signIn, signOut, accountInfo** que foram desenvolvidos no **auth.js**, com esses dois arquivos **auth.js** e **index.js** criamos um conceito no ReactJS que é chamado de **contextAPI** onde é possível criar funções específicas e que serão utilizadas em mais de um lugar do projeto.

No **auth.js** usamos alguns dados que são acessos do Azure AD e uma configuração para recuperar informações do usuário que são disponibilizadas pela lib **@microsoft/microsoft-graph-client** após o usuário realizar o login na aplicação.

E para isso vamos criar uma pasta **utils** para fazer essas configurações.

No **configAzureAd.js** temos as informações do **clientId e redirectUri** que são disponibilizadas ao fazer o registro de um aplicativo no Azure AD e também temos a configuração do **loginRequest** onde falamos em qual escopo o projeto terá acesso, que nesse caso é apenas para leitura das informações desse usuário.

**`src/utils/configAzureAd.js`**
```sh
export const msalConfig = {
  auth: {
    clientId: process.env.REACT_APP_CLIENT_ID,
    redirectUri: process.env.REACT_APP_REDIRECT_URI,
  },
  cache: {
    cacheLocation: 'localStorage',
    storeAuthStateInCookie: false,
  },
}

export const loginRequest = {
  scopes: ['user.read'],
}
```

Para segurança dos dados sensíveis utilizados no **configAzureAd.js** foi criado um arquivo **.env** na raíz do projeto, essas informações estão disponíveis ao cadastrar um resgistro de aplicativo no Azure AD.

**`.env`**
```sh
# Config Azure AD
REACT_APP_CLIENT_ID=ID_do_aplicativo
REACT_APP_REDIRECT_URI=URIs_de_Redirecionamento
```

No **graphService.js** só fazemos uma validação com o token recuperado após o login do usuário para recuperar da API **graph** as informações do usuário como **nome e e-mail**.

**`src/utils/graphService.js`**
```sh
const graph = require('@microsoft/microsoft-graph-client');

function getAuthenticatedClient(accessToken) {
  const client = graph.Client.init({
    authProvider: (done) => {
      done(null, accessToken);
    }
  });

  return client;
}

export async function getUserDetails(accessToken) {
  const client = getAuthenticatedClient(accessToken);

  const user = await client
    .api('/me')
    .select('displayName,mail,userPrincipalName')
    .get();

  return user;
}
```

E para finalizar no **App.js** importamos do **Routes** as configurações de rotas que a aplicação terá acesso e também o **AppProvider** que vai disponibilizar os métodos de **signIn, signOut, accountInfo** que serão utilizados em outros componentes utilizando o **contextAPI** para passar as informações.

**`src/App.js`**
```sh
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';

import AppProvider from './hooks';

import Routes from './routes';

const App = () => (
  <Router>
    <AppProvider>
      <Routes />
    </AppProvider>
  </Router>
);

export default App;
```

E assim finalizamos o projeto com autenticação via Azure AD, trazendo uma visão de como funciona o Azure AD e suas configurações e o seu funcionamento com o ReactJS. Espero ter ajudado!

Vale lembrar que com o próprio Azure AD você consegue criar outras configurações de permissões para quem vai acessar a aplicação, criação de grupos de usuários, e até mesmo fazer configurações para validar o token do login em uma API Rest criada pelo back end por exemplo.



Referência:
https://docs.microsoft.com/pt-br/graph/tutorials/react

O projeto completo pode ser baixado no github:
https://github.com/felipedesenna/react-authentication-azuread