En artículo anterior discutimos sobre este posible inconveniente cuando trabajamos con distintos proveedores de identidad y planteamos una propuesta de arquitectura combinando Conectores, Azure Functions y Microsoft Graph API..

En este post vamos a analizar qué son los Conectores y cómo nos pueden ser de gran ayuda para requerimientos de este tipo. Además crearemos un conector junto con una API muy simple utilizando Azure Functions.

Conectores

Ya vimos que los conectores nos proporcionan un mecanismo para ejecutar REST APIs antes de que Azure AD B2C haga la creación del usuario en la base de datos. Y vimos que se pueden ejecutar después de haber iniciado sesión con un proveedor de identidad externo y antes de crear el usuario.

Descubrimos además que sólo se ejecutan durante el registro de nuevos usuarios. Veamos entonces los aspectos técnicos más relevantes para poder completar nuestra implementación.

Casos de uso

Los conectores nos permiten extender la experiencia de registro e integrarla con sistemas externos:

  • Validación de datos: podemos validar determinados datos de entrada como puede ser el formato correcto de de identificación personal o cuenta bancaria.
  • Aprobación: en algunos casos prevenir la creación del usuario si no fue autorizado aún en una plataforma interna.
  • Agregar o sobrescribir atributos: podríamos sobrescribir algunos atributos, ya sea para darle un formato específico (Camel case). También podemos agregar o auto completar atributos.
  • Lógica de negocio: por ejemplo, notificar a otros sistemas externos.

En nuestro caso, validaremos contra Microsoft Graph si existe un usuario con ese email.

Solicitud (Request)

Cuando un conector es ejecutado envía una solicitud HTTP POST a la REST API que hemos configurado oportunamente. El cuerpo de la solicitud será enviado en formato JSON (Content-type: application/json), por ejemplo:

{
 "email": "johnsmith@facutherock.net",
 "identities": [
     {
     "signInType":"federated",
     "issuer":"github.com",
     "issuerAssignedId":"0123456789"
     }
 ],
 "displayName": "John Smith",
 "givenName":"John",
 "lastName":"Smith",
 "extension_<extensions-app-id>_Atributo1": "Atributo personalizo 2",
 "extension_<extensions-app-id>_Atributo2": "Atributo personalizo 2",
 "ui_locales":"es-ES"
}

Los atributos o claims que no tengan valor (null or undefined) no serán informados en el JSON enviado como solicitud.

Tipo de respuesta (Response)

Nuestra REST API debe devolver una respuesta al conector de Azure AD B2C que luego será utilizada para continuar con el flujo de usuario. La respuesta debe estar en formato JSON (Content-type: application/json).

Hay tres tipos de respuesta:

  • Respuesta de continuación:
    Indica que el flujo debe continuar a la siguiente etapa. Si estamos ejecutando un conector luego del inicio de sesión con un proveedor externo, la siguiente etapa será la recolección de atributos adicionales. Si el conector ejecutado es antes de crear el usuario, la etapa siguiente es la creación del usuario.
    El código de estado de la respuesta debe ser 200 - OK y debe contener un JSON con un elemento "action" cuyo valor debe ser "Continue", como el siguiente:
{
    "version": "1.0.0",
    "action": "Continue",
    "postalCode": "12349", // atributo adicional o auto-completado
    "extension_<extensions-app-id>_CustomAttribute": "value" // Atributo personalizado adicional o auto-completado
}
  • Respuesta de bloqueo:
    Detiene el flujo de usuario y muestra un mensaje al usuario. El código de estado de la respuesta debe ser 200 - OK y debe contener un JSON con un elemento "action" cuyo valor debe ser "ShowBlockPage", como el siguiente:
{
    "version": "1.0.0",
    "action": "ShowBlockPage",
    "userMessage": "Hubo un error.",
}
  • Respuesta de validación:
    Esta respuesta sólo es válida en la recolección de atributos adicionales y muestra un error al usuario en relación a la información ingresada. Por ejemplo, "Apellido es obligatorio".
    El código de estado de la respuesta debe ser 400 - Bad Request, debe contener un JSON con un elemento "action" cuyo valor debe ser "ValidationError" y otro elemento "status" cuyo valor debe ser 400 o "400", como el siguiente:
{
    "version": "1.0.0",
    "status": 400,
    "action": "ValidationError",
    "userMessage": "Apellido es obligatorio."
}

Definiciones

Para la implementación que necesitamos, donde sólo queremos evitar usuarios repetidos con el mismo email pero usando diferentes proveedores de identidad, usaremos respuestas de continuación y respuestas de bloqueo.

La lógica responderá de la soguiente forma:

  • Si existe un usuario con el email indicado, devolveremos "ShowBlockPage".
  • Si no existe un usuario con el email indicado, devolveremos "Continue".

Manos a la obra

En este post nos concentraremos sólo en los conectores y dejaremos para el próximo la lógica necesaria para interactuar con Microsoft Graph API.

  1. Crearemos una Azure Function para atender la petición del conector.
  2. Crearemos un conector y lo asociaremos a la Azure Function.
  3. Configuraremos el flujo de usuario para utilizar el conector.

Azure Function

Para no extender demasiado el texto ni desviar el foco, obviaré la creación y publicación de un proyecto de Azure Functions. Si necesitas ayuda con eso, podrás encontrarlo aquí.

Utilizaremos una función del tipo HttpTrigger. Por el momento la lógica de la función será muy simple:

  • Si el dominio del email es "gmail.com" entonces devolvemos "ShowBlockPage" con el mensaje adicional “Usuario duplicado".
  • En cualquier otro caso devolvemos "Continue".
[FunctionName("PreventUserDuplication")]
public static IActionResult Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")] dynamic req)
{
    if (req.email.EndsWith("gmail.com"))
    {
        return new OkObjectResult(
            new
            {
                version = "1.0.0",
                action = "ShowBlockPage",
                userMessage = $"Email {req.email} is duplicated."
            });
    }
    return new OkObjectResult(
        new
        {
            version = "1.0.0",
            action = "Continue"
        });
}

Finalmente debemos publicar el código y obtener la URL correspondiente ya que la necesitaremos en el siguiente paso.

La implementación que estamos realizando es solo “de muestra” y no es la implementación final. La idea es entender como funcionan los conectores.

Crear conector

Desde la sección Conectores de API agregaremos un nuevo conector.

Conectores de API
Conectores de API
Configuración de conector
Configuración de conector

En la pantalla de configuración del nuevo conector elegiremos un nombre para mostrar y luego completaremos el campo Dirección URL del punto de conexión con la URL de la Azure Function del punto anterior.

Tipo de autenticación usaremos Básico y para Nombre de Usuario y Contraseña pondremos cualquier valor , más adelante veremos por qué.

Al finalizar guardamos los cambios y terminamos.

Flujo de usuario

Flujo de usuario - Conectores
Flujo de usuario – Conectores

La configuración del flujo de usuario es bastante simple, solo debemos seleccionar que conector usar y en qué parte del flujo.

Siguiendo con nuestro, vamos a utilizar el mismo conector en ambas instancias, luego del inicio de sesión con un proveedor externo y antes de la ceación del usuario.

Pruebas

Para las pruebas intentaremos registrar un nuevo usuario con una cuenta de email terminada en “gmail.com” y otra terminada con cualquier dominio.

El flujo de usuario debería bloquear al usuario si la cuenta de email es de “gmail.com” y debería continuar con otras cuentas.

Usuario duplicado
Usuario duplicado

Para proveedores de identidad externos el comportamiento será el mismo.

Seguridad

Al momento de escribir este post, los conectores aceptan como mecanismo de seguridad autenticación básica o certificados (preview).

El mecanismo que elegimos anteriormente es Básico, el cuál requiere Usuario y Contraseña.

Es responsabilidad de la API validar correctamente el usuario y contraseña al recibir el request.

La ventaja de utilizar Azure Functions es que podemos utilizar la seguridad integrada que el servicio ofrece mediante llaves de función (Function Keys)

Conclusión

Hemos implementado en forma satisfactoria nuestra primera integración a través de Conectores de API, y si bien la implementación es solo “de muestra”, nos permitió entender como funcionan.

Para poder completar la implementación del requerimiento sólo nos falta completar la implementación de la Azure Function para integrarnos con Microsoft Graph API, te lo cuento en el próximo post!