Saltar a contenido

React

Azure MSAL

Introducción a la biblioteca de autenticación de Microsoft (MSAL)

MSAL permite a los desarrolladores adquirir tokens de la plataforma de identidad de Microsoft para autenticar a los usuarios y acceder a las API web seguras.

Se puede utilizar para proporcionar acceso seguro a Microsoft Graph, otras API de Microsoft, API web de terceros o su propia API web.

Adquiera y almacene tokens

Los tokens de acceso permiten a los clientes llamar de forma segura a las API web protegidas por Azure.

Hay varias formas de adquirir un token, algunas requieren la interacción del usuario a través de un navegador web, mientras que otros no requieren la interacción del usuario. En general, el método utilizado para adquirir un token depende de si la aplicación es una aplicación de cliente pública (como una aplicación de escritorio o móvil), o una aplicación de cliente confidencial (como una aplicación web, una API web o una aplicación daemon).

En esta ocacion vamos crear una aplicación de página única (SPA) de React que inicia la sesión de los usuarios y llama a Microsoft Graph mediante el flujo de código de autorización con PKCE. La aplicación de página única que cree usa la biblioteca de autenticación de Microsoft (MSAL) para React.

Logo


Pre requisitos:

  • Tener Git Instalado
  • Tener Node.js
  • Visual Studios Code o algun otro editor de codigo

Obtención del código de ejemplo completado

Vamos a clonar el siguente repo con el comando:

git clone https://github.com/architecture-it/msal-browser-react.git

El cual tiene la siguiente estructura de archivos:

  📁msal-browser-react
  ├─── 📁.github
  ├─── 📁.vscode
  ├─── 📁public
  ├─── 📁src 
  │     ├─── 📁@types
  │     ├─── 📁components
  │     ├─── 📁constants
  │     │     └───📃AUTH.ts
  │     ├─── 📁hooks
  │     │     └─── 📁use-token
  │     │           └───📃index.tsx
  │     ├─── 📁layout
  │     ├─── 📁pages
  │     │     ├─── 📁Home
  │     │     │     ├───📃index.tsx
  │     │     │     └───📃Home.module.scss
  │     │     └─── 📁User
  │     │           ├───📃index.tsx
  │     │           └───📃User.module.scss
  │     ├─── 📁routes
  │     │     ├─── 📃index.tsx
  │     │     └─── 📃ProtectedRoute.tsx
  │     ├─── 📁skeletons
  │     │     └─── 📁Principal
  │     │           ├───📃index.tsx
  │     │           └───📃Principal.module.scss
  │     ├─── services
  │     │     └───📃ServiceBase.ts
  │     ├─── 📁test-utils
  │     ├─── 📃App.tsx
  │     ├─── 📃msalInstance.js
  │     ├─── 📃index.tsx
  │     ├─── 📃react-app-env.d.ts
  │     ├─── 📃sample.test
  ├─── .dockerignore
  ├─── .env
  ├─── 📃.eslintrc.js
  ├─── .gitignore
  ├─── 📃Dockerfile
  ├─── 📃package-lock.json
  ├─── 📃package.json
  ├─── 📃stylelint.config.js
  └─── 📃tsconfig.json

Procedemos a correr el comando npm i, para instalar todos los paquetes de node.

Configuración de JavaScript SPA

En la carpeta src un archivo llamado msalInstance.js contiene la instancia de msal:

msalInstance.ts
import { PublicClientApplication } from "@azure/msal-browser";
import {
  addDefaultCallbacks,
  addToStorageCallback,
  addApmEventCallback,
} from "@architecture-it/azure-b2c/eventCallbacks";
import { msalConfig } from "@architecture-it/azure-b2c/config";

import rum from "./monitor";

/**
 * Debe ser istanciado fuera del ciclo de vida de los componentes para evitar reisntanciar cuando rerenderice la app
 * For more, visit: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-react/docs/getting-started.md
 */
const instance = new PublicClientApplication(msalConfig);

addDefaultCallbacks(instance);

instance.addEventCallback(addToStorageCallback());

instance.addEventCallback(addApmEventCallback(rum));

export const msalInstance = instance;

El valor de los parámetros de configuración se encuentran en el archivo .env

Nombre Descripción
clientId Identificador de la aplicación (cliente) de la aplicación que registró.
authorityDomain Instancia en la nube de Azure en la que se registra la aplicación.
signUpSignIn, forgotPassword, editProfile Flujos de inicio de sesion, recuperación de contraseña y editor de perfil.
redirectUri Dirección a la que volvera la el usuarios despues de cada acción

¿Que es JWT?

JWT (JSON Web Token) es un estándar qué está dentro del documento RFC 7519.

En el mismo se define un mecanismo para poder propagar entre dos partes, y de forma segura, la identidad de un determinado usuario, además con una serie de claims o privilegios.

Estos privilegios están codificados en objetos de tipo JSON, que se incrustan dentro de del payload o cuerpo de un mensaje que va firmado digitalmente.

JWT no es un estándar de autenticación, sino que simplemente un estándar que nos permite hacer una comunicación entre dos partes de identidad de usuario. Con este proceso, además, podríamos implementar la autenticación de usuario de una manera que fuera segura.

Logo


Puede encontrar más sobre esto en la documentación de Microsoft - Link

Eventos de MSAL

Podemos añadir funcionalidades según ciertos eventos internos de la librería con addEventCallback. Una vez que un usuario inicia sesión, Se puede por ejemplo configurar la cuenta default y hacer algo con el token.

Todo esto ya viene integrado a la librería @architecture-it/azure-b2c por lo que solo debemos agregar el siguiente archivo en src

src/msalInstance.ts
import { PublicClientApplication } from "@azure/msal-browser";
import {
  addDefaultCallbacks,
  addToStorageCallback,
  addApmEventCallback,
} from "@architecture-it/azure-b2c/eventCallbacks";
import { msalConfig } from "@architecture-it/azure-b2c/config";

//importar rum desde el archivo monitor del apm. (si se tiene configurado)
// import rum from "./monitor";

/**
 * Debe ser istanciado fuera del ciclo de vida de los componentes para evitar reisntanciar cuando rerenderice la app
 * For more, visit: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-react/docs/getting-started.md
 */
const instance = new PublicClientApplication(msalConfig);

addDefaultCallbacks(instance);

instance.addEventCallback(addToStorageCallback());

// Agrega el evento para setear el contexto del usuario en el apm
// instance.addEventCallback(addApmEventCallback(rum));

export const msalInstance = instance;
src/index.tsx
import { createRoot } from "react-dom/client";
import { StyleSystemProvider } from "@architecture-it/stylesystem";
import { CssBaseline } from "@mui/material";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import { store } from "store/store";

//descomentar si se tiene APM configurado
// import rum from "./monitor";
import App from "./App";
import { msalInstance } from "./msalInstance";

//necesario para el apm
rum.setInitialPageLoadName("Home");

const container = document.getElementById("root") as HTMLElement;
const root = createRoot(container);

root.render(
  <BrowserRouter>
    <Provider store={store}>
      <StyleSystemProvider>
        <CssBaseline />
        <App msalInstance={msalInstance} />
      </StyleSystemProvider>
    </Provider>
  </BrowserRouter>
);

Y por último agregar el clientId y el dominio a los environments, que correspondan

.env
# B2C Configuration
REACT_APP_B2C_CLIENT_ID=<XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX>
REACT_APP_B2C_AUTH_DOMAIN=<DOMAIN>

Consumo de Token para peticiones al Back

En conjunto a la librería @architecture-it/core y su serviceBase podemos consumir el token desde el sessionStorage o el localStorage

src/services/PersonService.ts
import type { IPerson } from "interfaces/Person";
import { ServiceBase } from "@architecture-it/core";
import env from "@architecture-it/react-env";
import { msalInstance } from "msalInstance";
import axios from "axios";
import { addResponseInterceptorRefreshToken } from "@architecture-it/azure-b2c";

export interface ICommonOptions {
  signal?: AbortSignal;
}

const BASE_URL = env("API") + "person";

class _PersonService extends ServiceBase {
  constructor() {
    super(BASE_URL);

    addResponseInterceptorRefreshToken(this.client, msalInstance, axios);
  }

  getAll = ({ signal }: ICommonOptions) => this.client.get<IPerson[]>("/", { signal });

}

const PersonService = new _PersonService();

export default PersonService;

Links usados y mas información: