Saltar al contenido principal

Implementación de la API en Node.js (SPAs + API)

Este documento forma parte del escenario de arquitectura SPA + API y explica cómo implementar la API en Node.js. Consulta el escenario para obtener más información sobre la solución implementada. El código fuente completo de la implementación de la API en Node.js está disponible en este repositorio de GitHub.

Paso 1. Defina los endpoints de la API

Usaremos el Express web application framework para crear nuestra API de Node.js.

Cree un archivo package.json

Cree una carpeta para su API, acceda a ella y ejecute npm init. Esto configurará el archivo package.json. Puede dejar la configuración predeterminada o cambiarla según prefiera. El archivo package.json de nuestro ejemplo es el siguiente:
{
  "name": "timesheets-api",
  "version": "1.0.0",
  "description": "API used to add timesheet entries for employees and contractors",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/auth0-samples/auth0-pnp-timesheets.git"
  },
  "author": "Auth0",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/auth0-samples/auth0-pnp-timesheets/issues"
  },
  "homepage": "https://github.com/auth0-samples/auth0-pnp-timesheets#readme"
}

Instala las dependencias

A continuación, debemos definir nuestras dependencias. Usaremos los siguientes módulos:
  • express: Este módulo agrega el framework web Express.
  • cors: Este módulo agrega compatibilidad para habilitar CORS, lo cual es necesario porque la API se llamará desde una aplicación de una sola página que se ejecuta en un dominio distinto dentro de un navegador web.
  • jwks-rsa: Esta biblioteca obtiene claves de firma RSA desde un endpoint de JWKS (JSON Web Key Set). Con expressJwtSecret, podemos generar un proveedor de secretos que proporcionará la clave de firma correcta a express-jwt según el kid del encabezado del . Para obtener más información, consulta el repositorio de GitHub de node-jwks-rsa.
  • express-jwt: Este módulo te permite autenticar solicitudes HTTP mediante tokens JWT en tus aplicaciones de Node.js. Proporciona varias funciones que facilitan el trabajo con JWT. Para obtener más información, consulta el repositorio de GitHub de express-jwt.
  • body-parser: Este es un middleware de análisis del cuerpo para Node.js. Extrae toda la parte del cuerpo de un flujo de solicitud entrante y la expone en req.body de una forma más fácil de usar. Para obtener más información y varias alternativas, consulta el repositorio de GitHub de body-parser.
Para instalar estas dependencias, ejecuta lo siguiente:
npm install express cors express-jwt jwks-rsa body-parser express-jwt-authz --save

Implementa los endpoints

Ve al directorio de tu API y crea un archivo server.js. Tu código debe:
  • Importar las dependencias.
  • Implementar los endpoints.
  • Iniciar el servidor de la API.
Esta es nuestra implementación de ejemplo:
const express = require('express');
const app = express();
const { expressjwt: jwt } = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const cors = require('cors');
const bodyParser = require('body-parser');

// Habilitar CORS
app.use(cors());

// Habilitar el uso del middleware de análisis del cuerpo de la solicitud
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

// Crear el endpoint de la API de registros de horas
app.post('/timesheets', function(req, res){
  res.status(201).send({message: "This is the POST /timesheets endpoint"});
})

// Iniciar el servidor de la API en localhost:8080
app.listen(8080);
Inicie su servidor de API con node server y haga una solicitud HTTP POST a localhost:8080/timesheets. Debería ver una respuesta JSON con el mensaje This is the POST /timesheets endpoint. Ahora ya tenemos nuestro endpoint, pero cualquiera puede llamarlo. Continúe con el siguiente párrafo para ver cómo podemos corregirlo.

Paso 2. Proteger los endpoints de la API

Para validar nuestro token, usaremos la función jwt, proporcionada por el middleware express-jwt, y jwks-rsa para obtener nuestro secreto. Las bibliotecas hacen lo siguiente:
  1. express-jwt decodificará el token y pasará la petición, el encabezado y la carga útil a jwksRsa.expressJwtSecret.
  2. jwks-rsa descargará todas las claves de firma desde el endpoint JWKS y comprobará si alguna coincide con el kid del encabezado del JWT. Si ninguna de las claves de firma coincide con el kid recibido, se generará un error. Si hay coincidencia, pasaremos la clave de firma correcta a express-jwt.
  3. express-jwt continuará con su propia lógica para validar la firma del token, la expiración, audience y issuer.
Los pasos que seguiremos en nuestro código son:
  • Crear la función de middleware para validar el .
  • Habilitar el uso del middleware en nuestras rutas.
También puedes escribir código para guardar el registro de horas en una base de datos. Esta es nuestra implementación de ejemplo (se omite parte del código por brevedad): Si iniciamos el servidor ahora y hacemos un HTTP POST a localhost:8080/timesheets deberíamos obtener el mensaje de error Missing or invalid token (lo cual es perfectamente normal, ya que no enviamos un token de acceso en la solicitud). Para probar también el caso en que sí funciona, debemos hacer lo siguiente:
  • Obtener un Token de acceso. Para ver más información sobre cómo hacerlo, consulta: Obtener un Token de acceso.
  • Invocar la API agregando un encabezado Authorization a la solicitud con el valor Bearer ACCESS_TOKEN (donde ACCESS_TOKEN es el valor del token que recuperamos en el primer paso).

Paso 3. Verifique los permisos de la aplicación

En este paso, añadiremos a nuestra implementación la capacidad de comprobar si la aplicación tiene los permisos (o scope) necesarios para usar nuestro endpoint y crear un registro de horas. En concreto, queremos asegurarnos de que el token tenga el scope correcto, que es batch:upload. Para ello, utilizaremos el paquete de Node.js express-jwt-authz, así que añádalo a su proyecto:
npm install express-jwt-authz --save
Ahora basta con agregar una llamada a jwtAuthz(...) en su middleware para garantizar que el JWT contenga un scope específico y así ejecutar un endpoint determinado. Agregaremos una dependencia adicional. La biblioteca express-jwt-authz, que se utiliza junto con express-jwt, valida el JWT y garantiza que tenga los permisos correctos para llamar al endpoint deseado. Para obtener más información, consulte el repositorio de GitHub de express-jwt-authz. Esta es nuestra implementación de ejemplo (se omite parte del código por brevedad):
// establecer dependencias - parte del código omitido
const jwtAuthz = require('express-jwt-authz');

// Habilitar CORS - código omitido

// Crear middleware para verificar el JWT - código omitido

// Habilitar el uso del middleware de análisis del cuerpo de la solicitud - código omitido

// crear el endpoint de la API de registros de horas
app.post('/timesheets', checkJwt, jwtAuthz(['create:timesheets'], { customUserKey: 'auth' }), function(req, res){
  var timesheet = req.body;

  // Guardar el registro de horas en la base de datos...

  //enviar la respuesta
  res.status(201).send(timesheet);
})

// iniciar el servidor de API en localhost:8080 - código omitido
Si invocamos nuestra API con un token que no incluye este scope, deberíamos obtener el mensaje de error Forbidden con el código de estado HTTP 403. Puede comprobarlo quitando este scope de su API.

Paso 4. Determinar la identidad del usuario

El middleware express-jwt, que se usa para validar el JWT, también asigna a req.auth la información contenida en el JWT. Si desea usar el claim sub para identificar al usuario de forma única, puede simplemente usar req.auth.sub. Sin embargo, en el caso de la aplicación de registro de horas, queremos usar la dirección de correo electrónico del usuario como identificador único. Lo primero que debemos hacer es escribir una Rule que agregue la dirección de correo electrónico del usuario al Token de acceso. Vaya a la sección Rules del Dashboard y haga clic en el botón Create Rule. Puede darle a la Rule un nombre descriptivo, por ejemplo Add email to Access Token, y luego usar el siguiente código para la Rule:
function (user, context, callback) {
  const namespace = 'https://api.exampleco.com/';
  context.accessToken[namespace + 'email'] = user.email;
  callback(null, user, context);
}
El namespace se usa para garantizar que el claim tenga un nombre único y no entre en conflicto con los nombres de ninguno de los claims estándar de OIDC. Sin embargo, Auth0 admite claims personalizados con y sin espacio de nombres. Para obtener más información sobre los claims personalizados, consulta Crear claims personalizados. A continuación, en tu API, puedes recuperar el valor del claim desde req.auth y usarlo como la identidad única del usuario, que puedes asociar a las entradas del registro de horas.
app.get('/timesheets', checkJwt, jwtAuthz(['read:timesheets'], { customUserKey: 'auth' }), function(req, res) {
  var timesheet = req.body;

  // Asociar el registro de horas con el usuario actual
  var userId = req.auth['https://api.exampleco.com/email'];
  timesheet.user_id = userId;

  // Guardar el registro de horas en la base de datos...

  //enviar la respuesta
  res.status(201).send(timesheet);
});