Aprovechamiento de OAuth en el edge

La autenticación es una de las utilidades más evidentes de la edge computing. Si logras saber quiénes son tus usuarios tan pronto y tan cerca de sus ubicaciones como sea posible, ganarás en rapidez y posibilidades de personalización. No obstante, existen varios enfoques para implantar un esquema de autenticación en el edge.

En nuestra entrada anterior, publicamos y analizamos una implementación de referencia para realizar autenticación con OAuth en el edge y que permitirá acceder a los tokens de seguridad del usuario actual. En esta entrada, vamos a aconsejarte cómo sacar partido de tu nueva pasarela de autenticación con cuatro casos de uso concretos y diferenciados.

Paywalls y otras decisiones avanzadas de autorización

En ocasiones, los sitios web toman decisiones basadas en complejos datos que no están disponibles en el edge. Las barreras de pago, o paywalls, son un ejemplo útil: quizá tengas que comprobar si un usuario dispone de crédito suficiente para «comprar» contenido, pero la información en los tokens de identidad de ese usuario no incluye el saldo actual.

Este es un caso de uso excelente que nos permite ilustrar la transmisión de información pertinente del id_token del usuario al origen, en forma de encabezados HTTP adicionales. El origen podrá entonces aprovechar esos datos para adoptar decisiones de acceso.

A continuación, te mostramos cómo difundir los datos del id_token entre varios encabezados de peticiones HTTP:

// Define a struct that groups together the pieces of data we care about.
#[derive(serde::Serialize, serde::Deserialize)]
struct IdTokenClaims {
    uuid: String,
    email: String,
    country: String,
}
// Validate the ID token, and destructure the claims we defined earlier.
match validate_token_rs256::<IdTokenClaims>(id_token, &settings) {
    Ok(claims) => {
        // Here, claims.custom is an instance of IdTokenClaims.
        req.set_header("Fastly-Auth-Uuid", claims.custom.uuid);
        req.set_header("Fastly-Auth-Email", claims.custom.email);
        req.set_header("Fastly-Auth-Country", claims.custom.country);
   }
    _ => {
        return Ok(responses::unauthorized("ID token invalid."));
    }
}

Cada vez que se utiliza un detalle concreto de los datos de perfil para adoptar una decisión de acceso, el origen puede responder con un encabezado Vary que ordena a Fastly almacenar la respuesta en la caché solo respecto a los usuarios cuyo estado de perfil coincida con el detalle:

Vary: Fastly-Auth-Uuid

La cobertura que hemos dado al uso del encabezado Vary es amplísima (nuestro colega Doc ya se refirió a ella en 2014; Andrew lo hizo hace poco): se trata de un potente mecanismo que, si se emplea de forma adecuada, puede aportar una mejora significativa al rendimiento de la caché en el edge.

Control de accesos pormenorizado para contenido estático

Supongamos que tus contenidos se alojan en un cubo estático como Amazon S3 o Google Cloud Storage. Si quisieras que solo determinados usuarios accedieran a algunos de esos contenidos, la cosa se complicaría. Como ya te hemos contado que Compute@Edge sirve —además de para diseñar, probar y desplegar código en nuestro entorno informático sin servidores— para distribuir contenido estático desde un proveedor de cubos, ¿por qué no tomar decisiones de acceso a partir de información etiquetada en el contenido, combinada con datos de autenticación ubicados en el edge?

  1. Añade un encabezado de respuesta HTTP Fastly-Require-Country a los objetos estáticos que manejes.

  2. Lee ese encabezado con la aplicación del edge al cargar desde el origen el objeto solicitado.

  3. Compara el valor con los datos recogidos en el id_token del usuario.

  4. Si no hay coincidencia, descarta la respuesta de contenido y, en su lugar, genera una respuesta en forma de error 403: Prohibido.

A continuación, te mostramos cómo podrías hacerlo por medio de la misma estructura IdTokenClaims del ejemplo anterior:

match validate_token_rs256::<IdTokenClaims>(id_token, &settings) {
    Ok(claims) => {
        let beresp = req.send("backend")?;
        if claims.custom.country != beresp.get_header_str("fastly-require-country").unwrap()
{
            return Ok(Response::from_status(StatusCode::FORBIDDEN));
        }
        return Ok(beresp);
    } 
    // ...
}

Actualización del acceso mediante autorizaciones incrementales

Pongamos por caso que ejecutas una aplicación de venta de entradas para eventos que utiliza Google como proveedor de identidades. Si al cliente lo único que le interesa es guardar eventos como marcadores, solo tienes que saber quién es; pero si quiere añadir un evento a su calendario Google Calendar, necesitas otro ámbito que te proporcione acceso de escritura al calendario. Posteriormente, cuando el usuario quiera hacer una reserva, necesitarás acceso al servicio de cartera o pagos del usuario, lo cual constituiría otro ámbito.

Quizás tenga sentido solicitar varios ámbitos a lo largo del proceso inicial de autorización, sobre todo los que se precisan con mayor frecuencia y que son menos delicados. Sin embargo, puede haber aspectos (p. ej., pagos) que no te interese que puedan ejecutarse en un origen sin la autorización pertinente; se trata de una forma inteligente de aplicar el principio de mínimo privilegio.

En casos como estos, el origen puede utilizar el access_token de la sesión actual para solicitar una autorización incremental al proveedor de identidades.

  1. En la aplicación del edge, añade el encabezado Fastly-Access-Token a la petición para permitir que el origen vea y utilice el token de acceso.

  2. Ayudándote del token de acceso, realiza peticiones en el origen directamente al proveedor de identidades (p. ej., para iniciar un pago).

  3. Si el proveedor de identidades deniega la petición debido a un ámbito insuficiente:

    1. el origen devuelve un error 403 a Fastly con el encabezado Fastly-Required-Scopes;

    2. Fastly inicia un flujo de autenticación nuevo para actualizar los tokens del usuario de modo que se habilite el ámbito nuevo y, asimismo, gestiona la devolución de llamada como de costumbre para sustituir la sesión del usuario con otra;

    3. y, por último, se redirige al usuario a la URL que precisaba el consentimiento actualizado, y el origen recibe un token de acceso nuevo con el que realizar una petición correcta al proveedor de identidades.

Entonces, solo nos falta reconocer en el edge una respuesta de error 403 que tenga un encabezado Fastly-Required-Scopes y activar el flujo nuevo:

// First, let’s make the configuration object mutable.
let mut settings = Config::load();
let beresp = req.send("backend")?;
if beresp.get_status() == fastly::http::StatusCode::FORBIDDEN {
    if let Some(incremental_scopes) = beresp.get_header_str("fastly-required-scopes") {
// Append the incremental scopes to the original settings.
        settings.config.scope.push(' ');
        settings.config.scope.push_str(incremental_scopes);
    } else {
        return Ok(beresp);
    }
}

Bloqueo de usuarios maliciosos

Por la razón que sea, en algún momento te podría interesar impedir el acceso de usuarios a tu aplicación. En este caso hay una solución intermedia: aunque los tokens de sesión de larga duración son prácticos, no es deseable que el token de sesión de un usuario bloqueado siga siendo válido durante dos semanas sin que pueda anularse. Por otro lado, la verificación de la sesión de todos los usuarios antes de cada operación puede ralentizar el proceso y, además, quizás no tengas capacidad para almacenar en la caché contenido ubicado en el edge.

OAuth en el edge te permite optar por validar el access_token ante el proveedor de identidades con cada petición —lo cual suele efectuarse con mucha celeridad si está optimizado para distribuir peticiones de este tipo por todo el mundo— y, a continuación, utilizar el contenido almacenado en la caché para atender dicha petición. De este modo, si revocas el acceso de un usuario en el proveedor de identidades, el usuario quedará bloqueado de inmediato.

En nuestra aplicación de ejemplo, hemos incluido esa llamada de verificación en tiempo real al proveedor de identidades con cada petición:

let mut userinfo_res = Request::get(settings.openid_configuration.userinfo_endpoint)
    .with_header(AUTHORIZATION, format!("Bearer {}", access_token))
    .send("idp")?;
if userinfo_res.get_status().is_client_error() {
    return Ok(responses::unauthorized(userinfo_res.take_body()));
}

Aunque esta solución viene a delegar el problema de validación de sesiones en tiempo real a un proveedor, el resultado es que este añade una latencia mínima a cada petición. Se trata de una solución intermedia: si prefieres un recorte de la latencia a cambio de un ligero incremento del retardo en las sesiones de revocación, Fastly puede almacenar en caché en el edge las respuestas del proveedor de identidades durante un corto periodo.

También ofrecemos servicios de purga de contenidos almacenados en caché en todo el mundo en unos 150 ms. Así que, si tienes la capacidad de coordinar la revocación de sesiones en el proveedor de identidades con el envío de una petición de purga a la API de Fastly, podrás disfrutar de las ventajas de ambas vertientes: la validación de sesiones se traduce en aciertos de caché allí donde sea posible, y a pesar de todo la revocación es posible en cuestión de escasos segundos.

Da rienda suelta a tu creatividad

Si hablamos de la seguridad de las aplicaciones web, la adopción de enfoques de autoservicio suele tener mala prensa, sobre todo cuando a un ingeniero se le ocurre utilizar «un cifrado propio».  

No obstante, si bien hay varios aspectos de la seguridad que es mejor dejar en manos de implementaciones sólidas y probadas en el terreno, el modo de aprovechar datos de autenticación y autorización para controlar tu aplicación es una cuestión que solo depende de ti. En esta entrada hemos analizado algunas ideas, pero existen miles de estrategias para la toma de decisiones respecto a la concesión de derechos y permisos de usuarios.

Si tienes alguna aportación interesante, no dudes en tuitearla mencionando @fastly.

Dora Militaru
Developer Relations Engineer
Andrew Betts
Head of Developer Relations
Fecha de publicación:

6 min de lectura

Comparte esta entrada
Dora Militaru
Developer Relations Engineer

Dora es Developer Relations Engineer en Fastly. Hizo sus pinitos como desarrolladora creando un sitio web de noticias de escala mundial y trabajó la compasión liderando equipos de ingeniería de fiabilidad de sitios y de protección de datos. Desde su diminuta mesa de cocina en Londres, sueña con ayudar a que la web sea más rápida, segura y fiable —mejor, a fin de cuentas— para todo el mundo.

Andrew Betts
Head of Developer Relations

Andrew Bett es Head of Developer Relations en Fastly y colabora con desarrolladores de todo el mundo para contribuir a que la web sea más rápida, segura, fiable y manejable. Fundó una consultora web que acabó adquiriendo el Financial Times, dirigió un equipo que creó la pionera aplicación web HTML5 del FT y fundó la división Labs dentro de este rotativo. Además, es miembro electo del Technical Architecture Group del W3C, comité compuesto por nueve personas que proporcionan orientación sobre el desarrollo de internet.

¿List@ para empezar?

Ponte en contacto o crea una cuenta.