Integrar tu aplicación PHP con Azure Active Directory

Hace poco te conté cómo podías integrar Azure Active Directory con React.js, así como con Azure Directory B2C. Hoy le toca el turno a las aplicaciones en PHP 🙂 Si bien es cierto que Microsoft no provee directamente una librería para este fín, existen diferentes soluciones en la comunidad que nos permiten este cometido. En este post te quiero mostrar cómo integrar PHP con Azure Active Directory.

Por ahora la solución que más me ha gustado es la que ofrece HWIOAuthBundle. Dispone de un montón de integraciones y entre ellas está la de Microsoft Azure.

Para verlo con un ejemplo, voy a crear una aplicación con Symfony y voy a configurar dicha librería. Lo primero que vamos a hacer es crear el esqueleto de un proyecto con este framework.

composer create-project symfony/skeleton php-loves-azure

Instala las siguientes librerías para poder utilizar algunos de los bundles de Symfony:

composer require annotations validator template asset security-bundle
composer require --dev  maker-bundle symfony/web-server-bundle

Para poder comprobar que la integración con Azure Active Directory funciona correctamente, vamos a crear un par de controladores: PublicController y PrivateController:

php bin/console make:controller PublicController
php bin/console make:controller PrivateController

Modifica la ruta de PublicController para que sea la página de inicio:

<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class PublicController extends AbstractController
{
    /**
     * @Route("/", name="public")
     */
    public function index()
    {
        return $this->render('public/index.html.twig', [
            'controller_name' => 'PublicController',
        ]);
    }
}

Ahora vamos a configurar todo lo necesario para HWIOAuthBundle. Lo primero que necesitas es instalar las siguientes librerías en este orden:

composer require php-http/guzzle6-adapter=1.1.1
composer require hwi/oauth-bundle php-http/httplug-bundle guzzlehttp/psr7

Es muy importante que instales la versión 1.1.1 de php-http/guzzle6-adapter, de lo contrario no funcionará.

Ahora comenzamos con los archivos de configuración. Abre el archivo config/routes.yaml y modificaló con el siguiente contenido:

# app/config/routes.yaml
hwi_oauth_redirect:
    resource: "@HWIOAuthBundle/Resources/config/routing/redirect_41.xml"
    prefix:   /connect
 
hwi_oauth_connect:
    resource: "@HWIOAuthBundle/Resources/config/routing/connect_41.xml"
    prefix:   /connect
 
hwi_oauth_login:
    resource: "@HWIOAuthBundle/Resources/config/routing/login_41.xml"
    prefix:   /login
 
azure_login:
    path:    /callback
 
azure_logout:
    path: /logout

Crear un nuevo archivo en la ruta config/packages/hwi_oauth.yaml y añade lo siguiente:

hwi_oauth:   
    firewall_names: [secured_area]
    resource_owners:
        azure:
            type:                azure            
            client_id:           "%env(AZURE_CLIENT_ID)%"
            client_secret:       "%env(AZURE_CLIENT_SECRET)%"
            
            options:
                resource:   https://graph.windows.net
                application: common

Como ves, en él estamos estableciendo cuál es el proveedor que vamos a utilizar, en este caso azure y debemos especificar el Client ID y el Client Secret de la aplicación que debemos registrar en nuestro directorio activo. Si todavía no la tienes, basta con ir al portal de Microsoft Azure, accede Azure Active Directory y selecciona la sección App Registrations haz clic en el botón + New registration application para añadir uno nuevo:

Registra la aplicación en Azure AD con la URL de tu aplicación + /callback

Copia el Application ID de la aplicación registrada, en el apartado Overview, y crea una nueva clave en el apartado Settings > Keys. Pega estos dos valores en el archivo .env con las claves AZURE_CLIENT_ID AZURE_CLIENT_SECRET respectivamente. Para terminar, en el apartado
Required permissions haz clic en Grant Permisos para que la aplicación pueda acceder a los recursos de la organización.

Añade en el archivo config/bundles.php la última linea para registrar la librería de HWI:

<?php
return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
    Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
    Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
    Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
    Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true],
    Http\HttplugBundle\HttplugBundle::class => ['all' => true],
    HWI\Bundle\OAuthBundle\HWIOAuthBundle::class => ['all' => true]
];

Para finalizar, configura el archivo config/packages/security.yaml donde harás uso de HWI como provider y configurarás el control de acceso a tu aplicación. En este ejemplo restringiremos el acceso al area /private.

# app/config/packages/security.yaml
security:
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        hwi:
            id: hwi_oauth.user.provider
    firewalls:
        secured_area:
            anonymous: ~
            oauth:
                resource_owners:                    
                    azure: "/callback"
                login_path:        /login
                use_forward:       false
                failure_path:      /login
                oauth_user_provider:
                    service: hwi_oauth.user.provider
            logout:
                path:   /logout
                target: /
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: true
            # activate different ways to authenticate
            # http_basic: true
            # https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
            # form_login: true
            # https://symfony.com/doc/current/security/form_login_setup.html
    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/private, roles: ROLE_OAUTH_USER }

Si ahora ejecutas la aplicación comprobarás que no tienes problemas para acceder al controlador público, pero si intentas acceder a la sección http://127.0.0.1:8001/private no será posible y lo que ocurrirá es que te mostrará la lista de providers disponibles para logarte, en este caso Azure.

Si haces clic en el enlace te redirigirá a la pantalla de login de Azure Active Directory donde una vez inicies sesión te devolverá a tu aplicación PHP con acceso a la zona privada.

El código de ejemplo lo tienes en mi GitHub.

¡Saludos!