Compartir a través de


Habilitación de la autenticación en su propia API web de Node.js mediante Azure Active Directory B2C

Importante

A partir del 1 de mayo de 2025, Azure AD B2C ya no estará disponible para la compra por parte de nuevos clientes. Obtenga más información en nuestras preguntas más frecuentes.

En este artículo, aprenderá a crear la aplicación web que llama a la API web. La API web debe protegerse mediante Azure Active Directory B2C (Azure AD B2C). Para autorizar el acceso a la API web, se procesarán las solicitudes que incluyen un token de acceso válido emitido por Azure AD B2C.

Prerrequisitos

Paso 1: Creación de una API web protegida

Siga estos pasos para crear la API web de Node.js.

Paso 1.1: Crear el proyecto

Use Express para Node.js para crear una API web. Para crear una API web, haga lo siguiente:

  1. Cree una nueva carpeta llamada TodoList.
  2. En la TodoList carpeta , cree un archivo denominado index.js.
  3. En un shell de comandos, ejecute npm init -y. Este comando crea un archivo package.json predeterminado para el proyecto de Node.js.
  4. En el shell de comandos, ejecute npm install express. Este comando instala el marco Express.

Paso 1.2: Instalación de dependencias

Agregue la biblioteca de autenticación al proyecto de la API web. La biblioteca de autenticación analiza el encabezado de autenticación HTTP, valida el token y extrae las notificaciones. Para obtener más información, revise la documentación de la biblioteca.

Para agregar la biblioteca de autenticación, instale los paquetes mediante la ejecución del siguiente comando:

npm install passport
npm install passport-azure-ad
npm install morgan

El paquete morgan es middleware de registrador de solicitudes HTTP para Node.js.

Paso 1.3: Escribir el código del servidor de API web

En el index.js archivo, agregue el código siguiente:

const express = require('express');
const morgan = require('morgan');
const passport = require('passport');
const config = require('./config.json');
const todolist = require('./todolist');
const cors = require('cors');

//<ms_docref_import_azuread_lib>
const BearerStrategy = require('passport-azure-ad').BearerStrategy;
//</ms_docref_import_azuread_lib>

global.global_todos = [];

//<ms_docref_azureadb2c_options>
const options = {
    identityMetadata: `https://${config.credentials.tenantName}.b2clogin.com/${config.credentials.tenantName}.onmicrosoft.com/${config.policies.policyName}/${config.metadata.version}/${config.metadata.discovery}`,
    clientID: config.credentials.clientID,
    audience: config.credentials.clientID,
    policyName: config.policies.policyName,
    isB2C: config.settings.isB2C,
    validateIssuer: config.settings.validateIssuer,
    loggingLevel: config.settings.loggingLevel,
    passReqToCallback: config.settings.passReqToCallback
}

//</ms_docref_azureadb2c_options>

//<ms_docref_init_azuread_lib>
const bearerStrategy = new BearerStrategy(options, (token, done) => {
        // Send user info using the second argument
        done(null, { }, token);
    }
);
//</ms_docref_init_azuread_lib>
const app = express();

app.use(express.json()); 

//enable CORS (for testing only -remove in production/deployment)
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Authorization, Origin, X-Requested-With, Content-Type, Accept');
    next();
});

app.use(morgan('dev'));

app.use(passport.initialize());

passport.use(bearerStrategy);

// To do list endpoints
app.use('/api/todolist', todolist);

//<ms_docref_protected_api_endpoint>
// API endpoint, one must present a bearer accessToken to access this endpoint
app.get('/hello',
    passport.authenticate('oauth-bearer', {session: false}),
    (req, res) => {
        console.log('Validated claims: ', req.authInfo);
    
          
        // Service relies on the name claim.  
        res.status(200).json({'name': req.authInfo['name']});
    }
);
//</ms_docref_protected_api_endpoint>

//<ms_docref_anonymous_api_endpoint>
// API anonymous endpoint, returns a date to the caller.
app.get('/public', (req, res) => res.send( {'date': new Date() } ));
//</ms_docref_anonymous_api_endpoint>

const port = process.env.PORT || 5000;

app.listen(port, () => {
    console.log('Listening on port ' + port);
});

Tome nota de los siguientes fragmentos de código en el index.jsarchivo:

  • Importa la biblioteca de Microsoft Entra de Passport

    const BearerStrategy = require('passport-azure-ad').BearerStrategy;
    
  • Establece las opciones de Azure AD B2C

    const options = {
        identityMetadata: `https://${config.credentials.tenantName}.b2clogin.com/${config.credentials.tenantName}.onmicrosoft.com/${config.policies.policyName}/${config.metadata.version}/${config.metadata.discovery}`,
        clientID: config.credentials.clientID,
        audience: config.credentials.clientID,
        policyName: config.policies.policyName,
        isB2C: config.settings.isB2C,
        validateIssuer: config.settings.validateIssuer,
        loggingLevel: config.settings.loggingLevel,
        passReqToCallback: config.settings.passReqToCallback
    }
    
  • Cree una instancia de la biblioteca de Microsoft Entra de Passport con las opciones de Azure AD B2C

    const bearerStrategy = new BearerStrategy(options, (token, done) => {
            // Send user info using the second argument
            done(null, { }, token);
        }
    );
    
  • Punto de conexión de API protegido. Atiende solicitudes que incluyen un token de acceso emitido por Azure AD B2C válido. Este punto de conexión devuelve el valor de la notificación name dentro del token de acceso.

    // API endpoint, one must present a bearer accessToken to access this endpoint
    app.get('/hello',
        passport.authenticate('oauth-bearer', {session: false}),
        (req, res) => {
            console.log('Validated claims: ', req.authInfo);
        
              
            // Service relies on the name claim.  
            res.status(200).json({'name': req.authInfo['name']});
        }
    );
    
  • Punto de conexión de API anónimo. La aplicación web puede llamarla sin presentar un token de acceso. Úselo para depurar la API web con llamadas anónimas.

    // API anonymous endpoint, returns a date to the caller.
    app.get('/public', (req, res) => res.send( {'date': new Date() } ));
    

Paso 1.4: Configuración de la API web

Agregue configuraciones a un archivo de configuración. El archivo contiene información sobre el proveedor de identidades de Azure AD B2C. La aplicación de API web usa esta información para validar el token de acceso que la aplicación web pasa como token de portador.

  1. En la carpeta raíz del proyecto, cree un config.json archivo y, a continuación, agréguelo al siguiente objeto JSON:

    {
        "credentials": {
            "tenantName": "fabrikamb2c",
            "clientID": "Enter_the_Application_Id_Here"
        },
        "policies": {
            "policyName": "B2C_1_susi"
        },
        "resource": {
            "scope": ["tasks.read"]
        },
        "metadata": {
            "authority": "login.microsoftonline.com",
            "discovery": ".well-known/openid-configuration",
            "version": "v2.0"
        },
        "settings": {
            "isB2C": true,
            "validateIssuer": true,
            "passReqToCallback": false,
            "loggingLevel": "info"
        }
    }
    
  2. En el config.json archivo, actualice las siguientes propiedades:

Sección Clave Importancia
credenciales Nombre del Inquilino Primera parte del nombre de inquilino de Azure AD B2C (por ejemplo, fabrikamb2c).
credenciales ID del cliente Id. de aplicación de la API web. Para obtener información sobre cómo obtener el identificador de registro de la aplicación de API web, consulte Requisitos previos.
políticas nombre de la política Flujos de usuario o directiva personalizada. Para obtener información sobre cómo obtener el flujo de usuario o la directiva, consulte Requisitos previos.
recurso alcance Ámbitos del registro de la aplicación de API web, como [tasks.read]. Para obtener información sobre cómo obtener el ámbito de la API web, consulte Requisitos previos.

Paso 2: Crear la aplicación web Node.js

Siga estos pasos para crear la aplicación web Node. Esta aplicación web autentica a un usuario para adquirir un token de acceso que se usa para llamar a la API web de Node que creó en el paso 1:

Paso 2.1: Crear el proyecto de nodo

Cree una carpeta para contener la aplicación de nodo, como call-protected-api.

  1. En el terminal, cambie el directorio a la carpeta de la aplicación del nodo, como cd call-protected-apiy ejecute npm init -y. Este comando crea un archivo package.json predeterminado para el proyecto de Node.js.

  2. En el terminal, ejecute npm install express. Este comando instala el marco Express.

  3. Cree más carpetas y archivos para lograr esta estructura de proyecto:

    call-protected-api/
    ├── index.js
    └── package.json
    └── .env
    └── views/
        └── layouts/
            └── main.hbs
        └── signin.hbs
        └── api.hbs
    

    La views carpeta contiene archivos de controladores para la interfaz de usuario de la aplicación web.

Paso 2.2: Instalación de las dependencias

En el terminal, instale los dotenvpaquetes , express-handlebars, express-sessiony @azure/msal-node mediante la ejecución de los siguientes comandos:

npm install dotenv
npm install express-handlebars
npm install express
npm install axios
npm install express-session
npm install @azure/msal-node

Paso 2.3: Compilación de componentes de la interfaz de usuario de la aplicación web

  1. En el main.hbs archivo, agregue el código siguiente:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
        <title>Azure AD B2C | Enable authenticate on web API using MSAL for B2C</title>
    
        <!-- adding Bootstrap 4 for UI components  -->
        <!-- CSS only -->
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
        <link rel="SHORTCUT ICON" href="https://c.s-microsoft.com/favicon.ico?v2" type="image/x-icon">
      </head>
      <body>
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
          <a class="navbar-brand" href="/">Microsoft Identity Platform</a>
            {{#if showSignInButton}}
                <div class="ml-auto">
                    <a type="button" id="SignIn" class="btn btn-success" href="/signin" aria-haspopup="true" aria-expanded="false">
                        Sign in to call PROTECTED API
                    </a>
                    <a type="button" id="SignIn" class="btn btn-warning" href="/api" aria-haspopup="true" aria-expanded="false">
                        Or call the ANONYMOUS API 
                     </a>
                </div>
            {{else}}
                    <p class="navbar-brand d-flex ms-auto">Hi {{givenName}}</p>
                    <a class="navbar-brand d-flex ms-auto" href="/signout">Sign out</a>
            {{/if}}
        </nav>
        <br>
        <h5 class="card-header text-center">MSAL Node Confidential Client application with Auth Code Flow</h5>
        <br>
        <div class="row" style="margin:auto" >
          {{{body}}}
        </div>
        <br>
        <br>
      </body>
    </html>
    

    El main.hbs archivo está en la layout carpeta y debe contener cualquier código HTML necesario en toda la aplicación. Implementa la interfaz de usuario compilada con bootstrap 5 CSS Framework. Cualquier interfaz de usuario que cambie de página a página, como signin.hbs, se coloca en el marcador de posición que se muestra como {{{body}}}.

  2. En el signin.hbs archivo, agregue el código siguiente:

    <div class="col-md-3" style="margin:auto">
      <div class="card text-center">
        <div class="card-body">
          {{#if showSignInButton}}
    
          {{else}}
               <h5 class="card-title">You have signed in</h5>
              <a type="button" id="Call-api" class="btn btn-success" href="/api" aria-haspopup="true" aria-expanded="false">
                  Call the PROTECTED API
              </a>
          {{/if}}
        </div>
        </div>
      </div>
    </div>
    
  3. En el api.hbs archivo, agregue el código siguiente:

    <div class="col-md-3" style="margin:auto">
      <div class="card text-center bg-{{bg_color}}">
        <div class="card-body">
    
              <h5 class="card-title">{{data}}</h5>
    
        </div>
      </div>
    </div>
    

    En esta página se muestra la respuesta de la API. El bg-{{bg_color}} atributo de clase en la tarjeta de Bootstrap permite a la interfaz de usuario mostrar un color de fondo diferente para los distintos puntos de conexión de API.

Paso 2.4: Código completo del servidor de aplicaciones web

  1. En el archivo .env, agregue el siguiente código, que incluye el puerto http del servidor, los detalles del registro de aplicaciones y los detalles del flujo de inicio de sesión y registro de usuarios/política.

    SERVER_PORT=3000
    #web apps client ID
    APP_CLIENT_ID=<You app client ID here>
    #session secret
    SESSION_SECRET=sessionSecretHere
    #web app client secret
    APP_CLIENT_SECRET=<Your app client secret here>
    #tenant name
    TENANT_NAME=<your-tenant-name>
    #B2C sign up and sign in user flow/policy name and authority
    SIGN_UP_SIGN_IN_POLICY_AUTHORITY=https://<your-tenant-name>.b2clogin.com/<your-tenant-name>.onmicrosoft.com/<sign-in-sign-up-user-flow-name>
    AUTHORITY_DOMAIN=https://<your-tenant-name>.b2clogin.com
    #client redorect url
    APP_REDIRECT_URI=http://localhost:3000/redirect
    LOGOUT_ENDPOINT=https://<your-tenant-name>.b2clogin.com/<your-tenant-name>.onmicrosoft.com/<sign-in-sign-up-user-flow-name>/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000
    

    Modifique los valores de los .env archivos, tal y como se explica en Configuración de la aplicación web de ejemplo

  2. En el index.js archivo, agregue el código siguiente:

    /*
     * Copyright (c) Microsoft Corporation. All rights reserved.
     * Licensed under the MIT License.
     */
    require('dotenv').config();
    const express = require('express');
    const session = require('express-session');
    const {engine}  = require('express-handlebars');
    const msal = require('@azure/msal-node');
    //Use axios to make http calls 
    const axios = require('axios');
    
    //<ms_docref_configure_msal>
    /**
     * Confidential Client Application Configuration
     */
     const confidentialClientConfig = {
        auth: {
            clientId: process.env.APP_CLIENT_ID, 
            authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, 
            clientSecret: process.env.APP_CLIENT_SECRET,
            knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array
            redirectUri: process.env.APP_REDIRECT_URI,
            validateAuthority: false
        },
        system: {
            loggerOptions: {
                loggerCallback(loglevel, message, containsPii) {
                    console.log(message);
                },
                piiLoggingEnabled: false,
                logLevel: msal.LogLevel.Verbose,
            }
        }
    };
    
    // Initialize MSAL Node
    const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);
    //</ms_docref_configure_msal>
    // Current web API coordinates were pre-registered in a B2C tenant.
    
    //<ms_docref_api_config>
    const apiConfig = {
        webApiScopes: [`https://${process.env.TENANT_NAME}.onmicrosoft.com/tasks-api/tasks.read`],
        anonymousUri: 'http://localhost:5000/public',
        protectedUri: 'http://localhost:5000/hello'
    };
    //</ms_docref_api_config>
    
    /**
     * The MSAL.js library allows you to pass your custom state as state parameter in the Request object
     * By default, MSAL.js passes a randomly generated unique state parameter value in the authentication requests.
     * The state parameter can also be used to encode information of the app's state before redirect. 
     * You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
     * For more information, visit: https://docs.microsoft.com/azure/active-directory/develop/msal-js-pass-custom-state-authentication-request
     */
    const APP_STATES = {
        LOGIN: 'login',
        CALL_API:'call_api'   
    }
    
    
    /** 
     * Request Configuration
     * We manipulate these two request objects below 
     * to acquire a token with the appropriate claims.
     */
     const authCodeRequest = {
        redirectUri: confidentialClientConfig.auth.redirectUri,
    };
    
    const tokenRequest = {
        redirectUri: confidentialClientConfig.auth.redirectUri,
    };
    
    
    /**
     * Using express-session middleware. Be sure to familiarize yourself with available options
     * and set them as desired. Visit: https://www.npmjs.com/package/express-session
     */
     const sessionConfig = {
        secret: process.env.SESSION_SECRET,
        resave: false,
        saveUninitialized: false,
        cookie: {
            secure: false, // set this to true on production
        }
    }
    //Create an express instance
    const app = express();
    
    //Set handlebars as your view engine
    app.engine('.hbs', engine({extname: '.hbs'}));
    app.set('view engine', '.hbs');
    app.set("views", "./views");
    
    app.use(session(sessionConfig));
    
    /**
     * This method is used to generate an auth code request
     * @param {string} authority: the authority to request the auth code from 
     * @param {array} scopes: scopes to request the auth code for 
     * @param {string} state: state of the application, tag a request
     * @param {Object} res: express middleware response object
     */
    
     const getAuthCode = (authority, scopes, state, res) => {
        // prepare the request
        console.log("Fetching Authorization code")
        authCodeRequest.authority = authority;
        authCodeRequest.scopes = scopes;
        authCodeRequest.state = state;
    
        //Each time you fetch Authorization code, update the authority in the tokenRequest configuration
        tokenRequest.authority = authority;
    
        // request an authorization code to exchange for a token
        return confidentialClientApplication.getAuthCodeUrl(authCodeRequest)
            .then((response) => {
                console.log("\nAuthCodeURL: \n" + response);
                //redirect to the auth code URL/send code to 
                res.redirect(response);
            })
            .catch((error) => {
                res.status(500).send(error);
            });
    }
    
    app.get('/', (req, res) => {
        res.render('signin', { showSignInButton: true });
    });
    
    
    
    app.get('/signin',(req, res)=>{ 
            //Initiate a Auth Code Flow >> for sign in
            //Pass the api scopes as well so that you received both the IdToken and accessToken
            getAuthCode(process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY,apiConfig.webApiScopes, APP_STATES.LOGIN, res);
    });
    
    
    app.get('/redirect',(req, res)=>{    
        
        if (req.query.state === APP_STATES.LOGIN) {
            // prepare the request for calling the web API
            tokenRequest.authority = process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY;
            tokenRequest.scopes = apiConfig.webApiScopes;
            tokenRequest.code = req.query.code;
            confidentialClientApplication.acquireTokenByCode(tokenRequest)
            .then((response) => {
                req.session.accessToken = response.accessToken;
                req.session.givenName = response.idTokenClaims.given_name;
                console.log('\nAccessToken:' + req.session.accessToken);
                res.render('signin', {showSignInButton: false, givenName: response.idTokenClaims.given_name});
            }).catch((error) => {
                console.log(error);
                res.status(500).send(error);
            });
        }else{
            res.status(500).send('We do not recognize this response!');
        }
    });
    
    //<ms_docref_api_express_route>
    app.get('/api', async (req, res) => {
        if(!req.session.accessToken){
            //User is not logged in and so they can only call the anonymous API
            try {
                const response = await axios.get(apiConfig.anonymousUri);
                console.log('API response' + response.data); 
                res.render('api',{data: JSON.stringify(response.data), showSignInButton: true, bg_color:'warning'});
            } catch (error) {
                console.error(error);
                res.status(500).send(error);
            }         
        }else{
            //Users have the accessToken because they signed in and the accessToken is still in the session
            console.log('\nAccessToken:' + req.session.accessToken);
            let accessToken = req.session.accessToken;
            const options = {
                headers: {
                    //accessToken used as bearer token to call a protected API
                    Authorization: `Bearer ${accessToken}`
                }
            };
    
            try {
                const response = await axios.get(apiConfig.protectedUri, options);
                console.log('API response' + response.data); 
                res.render('api',{data: JSON.stringify(response.data), showSignInButton: false, bg_color:'success', givenName: req.session.givenName});
            } catch (error) {
                console.error(error);
                res.status(500).send(error);
            }
        }     
    });
    
    //</ms_docref_api_express_route>
    
    /**
     * Sign out end point
    */
    app.get('/signout',async (req, res)=>{    
        logoutUri = process.env.LOGOUT_ENDPOINT;
        req.session.destroy(() => {
            res.redirect(logoutUri);
        });
    });
    app.listen(process.env.SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT));
    

    El código del index.js archivo consta de variables globales y rutas rápidas.

    Variables globales:

    • confidentialClientConfig: el objeto de configuración de MSAL, que se usa para crear el objeto de aplicación cliente confidencial.

      /**
       * Confidential Client Application Configuration
       */
       const confidentialClientConfig = {
          auth: {
              clientId: process.env.APP_CLIENT_ID, 
              authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, 
              clientSecret: process.env.APP_CLIENT_SECRET,
              knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array
              redirectUri: process.env.APP_REDIRECT_URI,
              validateAuthority: false
          },
          system: {
              loggerOptions: {
                  loggerCallback(loglevel, message, containsPii) {
                      console.log(message);
                  },
                  piiLoggingEnabled: false,
                  logLevel: msal.LogLevel.Verbose,
              }
          }
      };
      
      // Initialize MSAL Node
      const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);
      
    • apiConfig: contiene la propiedad webApiScopes (cuyo valor debe ser una matriz), que representa los ámbitos configurados en la API web y que se otorgan a la aplicación web. También tiene URIs de la API web a la que se va a llamar, es decir anonymousUri y protectedUri.

      const apiConfig = {
          webApiScopes: [`https://${process.env.TENANT_NAME}.onmicrosoft.com/tasks-api/tasks.read`],
          anonymousUri: 'http://localhost:5000/public',
          protectedUri: 'http://localhost:5000/hello'
      };
      
    • APP_STATES: un valor incluido en la solicitud que también se devuelve en la respuesta del token. Se usa para diferenciar entre las respuestas recibidas de Azure AD B2C.

    • authCodeRequest: el objeto de configuración utilizado para recuperar el código de autorización.

    • tokenRequest: el objeto de configuración utilizado para adquirir un token por código de autorización.

    • sessionConfig: El objeto de configuración para la sesión de Express.

    • getAuthCode: método que crea la dirección URL de la solicitud de autorización, lo que permite al usuario escribir las credenciales y dar su consentimiento a la aplicación. Usa el getAuthCodeUrl método , que se define en la clase ConfidentialClientApplication .

    Rutas rápidas:

    • /:
      • Es la entrada a la aplicación de web y muestra la página signin.
    • /signin:
      • Inicia la sesión del usuario.
      • Llama al método getAuthCode() y le pasa el elemento authority para la directiva o flujo de usuario de inicio de sesión y registro, APP_STATES.LOGIN y apiConfig.webApiScopes.
      • Hace que se le pida al usuario final ingresar sus credenciales de acceso, o si el usuario no tiene una cuenta, puede registrarse.
      • La respuesta final resultante de este punto de conexión incluye un código de autorización de B2C devuelto al punto de conexión /redirect.
    • /redirect:
      • Es el punto de conexión establecido como URI de redirección para la aplicación web en Azure Portal.
      • Usa el parámetro de consulta state en la respuesta de Azure AD B2C para diferenciar entre las solicitudes realizadas desde la aplicación web.
      • Si el estado de la aplicación es APP_STATES.LOGIN, el código de autorización adquirido se usa para recuperar un token mediante el acquireTokenByCode() método . Al solicitar un token mediante el método acquireTokenByCode, se usan los mismos ámbitos que al adquirir el código de autorización. El token adquirido incluye un accessToken, idToken y un idTokenClaims. Después de adquirir accessToken, lo coloca en una sesión para su uso posterior para llamar a la API web.
    • /api:
      • Llama a la API web.
      • accessToken Si no está en la sesión, llame al punto de conexión de API anónimo (http://localhost:5000/public), de lo contrario, llame al punto de conexión de API protegido (http://localhost:5000/hello).
    • /signout:
      • Cierra la sesión del usuario.
      • borra la sesión de la aplicación web y realiza una llamada http al punto de conexión de cierre de sesión de Azure AD B2C.

Paso 3: Ejecución de la aplicación web y la API

Siga los pasos descritos en Ejecución de la aplicación web y la API para probar la aplicación web y la API web.

Pasos siguientes